SQL este contractul care supraviețuiește ORM-ului

2026-09-10

Fiecare produs pe care l-am lansat a supraviețuit cel puțin unei decizii privind frameworkul. Baza de date le-a supraviețuit tuturor. Se schimbă versiunile Next.js, ORM-ul și echipa; rândurile rămân, împreună cu ipotezele încorporate în schemă. De aceea tratez SQL-ul ca pe contractul real al produsului, nu ca pe un detaliu de implementare ascuns în spatele unei clase repository.

Prisma acoperă mare parte din munca mea de zi cu zi: CRUD tipizat, migrări în repository și o schemă pe care o poate citi un coleg nou. Dar citesc și SQL-ul generat. Dacă o pagină de listă face câte o interogare pentru fiecare rând, un raport are nevoie de o funcție de fereastră sau un dashboard cere GROUP BY peste trei join-uri, scriu manual interogarea cu $queryRaw ori o pun într-un view. Înainte să adauge cineva Redis, deschid EXPLAIN ANALYZE. În spatele celor mai multe tichete „ne trebuie cache” se află un index lipsă.

Constrângerile sunt cea mai ieftină asigurare de calitate. NOT NULL, UNIQUE, FOREIGN KEY și CHECK se aplică la fiecare scriere, din fiecare serviciu, inclusiv din scriptul rulat manual într-o zi de vineri. Zod protejează API-ul; baza de date protejează adevărul. Banii se stochează în unități minore întregi sau NUMERIC, niciodată float. Webhookurile de plată pot sosi de două ori, așa că cheia de idempotență are nevoie de un index unic, nu de un comentariu în handler.

Bugurile FinTech apar în zona concurenței. Două cereri citesc un sold, ambele decid că este suficient și ambele scriu — o actualizare pierdută, pe care nicio validare frontend nu o poate corecta. Prefer o singură instrucțiune atomică —UPDATE ... SET balance = balance - $1 WHERE id = $2 AND balance >= $1— apoi verific dacă s-a modificat exact un rând. Alternativ, folosesc o tranzacție cu SELECT ... FOR UPDATE și o tabelă ledger append-only, astfel încât fiecare mișcare să poată fi explicată ulterior. În sistemele care au procesat peste 1,5 milioane de dolari, „să poți explica ulterior” este esența muncii.

Indexurile trebuie să urmeze interogările executate în realitate, nu coloanele care par importante. În indexurile compuse vin mai întâi coloanele de egalitate, iar la final cea de interval sau sortare; indexurile parțiale sunt bune pentru rândurile „active”, iar keyset pagination este preferabilă lui OFFSET 50000. Migrările pe tabele active se fac în etape: adaugi coloana ca nullable, o completezi pe loturi, apoi pui constrângerea; construiești indexul cu CONCURRENTLY. O migrare care blochează tabelul de comenzi la prânz este o întrerupere însoțită de un pull request.

Refuz tabelul entity-attribute-value „flexibil” în locul unei scheme, folosirea JSONB pentru a ascunde câmpuri esențiale pe care nimeni nu vrea să le modeleze, soft delete peste tot fără să te gândești la constrângerile unice și un cache în fața unei interogări pe care nimeni n-a analizat-o cu EXPLAIN. Dacă îmi trimiți o pagină lentă sau o schemă incertă, trimite și interogarea și numărul de rânduri. Îți voi răspunde cu planul, indexul și prima constrângere pe care aș adăuga-o.

Oferă-i următorului produs cuiva care rămâne.

Ești gata să începem? Trimite-mi un mesaj - pot începe acum și pot rămâne alături de produs. Colaborări pe termen lung, nu o predare în două săptămâni. Să discutăm proiectul, cerințele și cea mai bună cale de urmat.

Întreabă despre experiență sau servicii