Experiență „Baza de date ca cod”.

Experiență „Baza de date ca cod”.

SQL, ce poate fi mai simplu? Fiecare dintre noi poate scrie o cerere simplă - tastăm selecta, enumerați apoi coloanele necesare din, numele tabelului, unele condiții în Unde și asta-i tot - datele utile sunt în buzunarul nostru și (aproape) indiferent de ce DBMS se află sub capotă în acel moment (sau poate nu este deloc un SGBD). Ca rezultat, lucrul cu aproape orice sursă de date (relațională și nu așa) poate fi luată în considerare din punctul de vedere al codului obișnuit (cu tot ceea ce implică - controlul versiunilor, revizuirea codului, analiză statică, autotestare și atât). Și acest lucru se aplică nu numai datelor în sine, schemelor și migrărilor, ci în general pentru întreaga durată de viață a stocării. În acest articol vom vorbi despre sarcinile de zi cu zi și problemele de lucru cu diferite baze de date sub lentila „bază de date ca cod”.

Și să începem chiar de la ORM. Primele bătălii de tip „SQL vs ORM” au fost observate din nou pre-Petrine Rus'.

Maparea obiect-relațională

Susținătorii ORM apreciază în mod tradițional viteza și ușurința de dezvoltare, independența față de DBMS și codul curat. Pentru mulți dintre noi, codul pentru lucrul cu baza de date (și adesea baza de date în sine)

de obicei arata cam asa...

@Entity
@Table(name = "stock", catalog = "maindb", uniqueConstraints = {
        @UniqueConstraint(columnNames = "STOCK_NAME"),
        @UniqueConstraint(columnNames = "STOCK_CODE") })
public class Stock implements java.io.Serializable {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "STOCK_ID", unique = true, nullable = false)
    public Integer getStockId() {
        return this.stockId;
    }
  ...

Modelul este agățat cu adnotări inteligente și undeva în spatele scenei un ORM curajos generează și execută tone de cod SQL. Apropo, dezvoltatorii fac tot posibilul să se izoleze de baza lor de date cu kilometri de abstracții, ceea ce indică unele „Ură SQL”.

De cealaltă parte a baricadelor, adepții SQL pur „făcut manual” remarcă capacitatea de a stoarce tot sucul din DBMS-ul lor fără straturi și abstracții suplimentare. Ca urmare, apar proiecte „data-centric”, în care în baza de date sunt implicați oameni special pregătiți (sunt și „baziciști”, sunt și „baziciști”, sunt și „basdeneri”, etc.), iar dezvoltatorii trebuie doar să „trageți” vizualizările gata făcute și procedurile stocate, fără a intra în detalii.

Și dacă am avea ce este mai bun din ambele lumi? Cum se face acest lucru într-un instrument minunat cu un nume care afirmă viața Dasql. Voi da câteva rânduri din conceptul general în traducerea mea gratuită și vă puteți familiariza cu el mai detaliat aici.

Clojure este un limbaj cool pentru a crea DSL-uri, dar SQL-ul în sine este un DSL cool și nu avem nevoie de altul. Expresiile S sunt grozave, dar nu adaugă nimic nou aici. Ca rezultat, primim paranteze de dragul parantezelor. Nu sunt de acord? Apoi așteptați momentul în care abstractizarea din baza de date începe să curgă și începeți să vă certați cu funcția (raw-sql)

Si ce ar trebui sa fac? Să lăsăm SQL ca SQL obișnuit - un fișier per cerere:

-- name: users-by-country
select *
  from users
 where country_code = :country_code

... și apoi citiți acest fișier, transformându-l într-o funcție obișnuită Clojure:

(defqueries "some/where/users_by_country.sql"
   {:connection db-spec})

;;; A function with the name `users-by-country` has been created.
;;; Let's use it:
(users-by-country {:country_code "GB"})
;=> ({:name "Kris" :country_code "GB" ...} ...)

Prin aderarea la principiul „SQL de la sine, Clojure de la sine”, obțineți:

  • Fără surprize sintactice. Baza de date (ca oricare alta) nu este 100% conformă cu standardul SQL - dar acest lucru nu contează pentru Yesql. Nu veți pierde niciodată timpul căutând funcții cu sintaxă echivalentă SQL. Nu va trebui niciodată să vă întoarceți la o funcție (raw-sql "unele('funky'::SYNTAX)")).
  • Cel mai bun suport pentru editor. Editorul dvs. are deja suport excelent pentru SQL. Salvând SQL ca SQL, îl puteți utiliza pur și simplu.
  • Compatibilitate cu echipa. DBA-urile dvs. pot citi și scrie SQL-ul pe care îl utilizați în proiectul dvs. Clojure.
  • Reglare mai ușoară a performanței. Trebuie să construiți un plan pentru o interogare problematică? Aceasta nu este o problemă atunci când interogarea dvs. este SQL obișnuită.
  • Reutilizarea interogărilor. Trageți și plasați aceleași fișiere SQL în alte proiecte, deoarece este pur și simplu vechi SQL - doar partajați-l.

În opinia mea, ideea este foarte cool și în același timp foarte simplă, datorită căreia proiectul a câștigat multe urmași într-o varietate de limbi. Și vom încerca în continuare să aplicăm o filozofie similară de a separa codul SQL de orice altceva cu mult dincolo de ORM.

Manageri IDE și DB

Să începem cu o sarcină simplă de zi cu zi. De multe ori trebuie să căutăm unele obiecte în baza de date, de exemplu, să găsim un tabel în schemă și să studiem structura acestuia (ce coloane, chei, indici, constrângeri etc. sunt folosite). Și de la orice IDE grafic sau un mic DB-manager, în primul rând, ne așteptăm exact la aceste abilități. Ca să fie rapid și să nu fii nevoit să aștepți o jumătate de oră până când se desenează o fereastră cu informațiile necesare (mai ales cu o conexiune lentă la o bază de date la distanță) și, în același timp, informațiile primite sunt proaspete și relevante, și nu junk stocate în cache. Mai mult, cu cât baza de date este mai complexă și mai mare și cu cât numărul acestora este mai mare, cu atât este mai dificil să faci acest lucru.

Dar de obicei arunc mouse-ul și doar scriu cod. Să presupunem că trebuie să aflați ce tabele (și cu ce proprietăți) sunt conținute în schema „HR”. În majoritatea SGBD-urilor, rezultatul dorit poate fi atins cu această interogare simplă din information_schema:

select table_name
     , ...
  from information_schema.tables
 where schema = 'HR'

De la bază de date la bază de date, conținutul unor astfel de tabele de referință variază în funcție de capacitățile fiecărui SGBD. Și, de exemplu, pentru MySQL, din aceeași carte de referință puteți obține parametrii de tabel specifici acestui DBMS:

select table_name
     , storage_engine -- Используемый "движок" ("MyISAM", "InnoDB" etc)
     , row_format     -- Формат строки ("Fixed", "Dynamic" etc)
     , ...
  from information_schema.tables
 where schema = 'HR'

Oracle nu știe information_schema, dar are Metadatele Oracle, și nu apar probleme mari:

select table_name
     , pct_free       -- Минимум свободного места в блоке данных (%)
     , pct_used       -- Минимум используемого места в блоке данных (%)
     , last_analyzed  -- Дата последнего сбора статистики
     , ...
  from all_tables
 where owner = 'HR'

ClickHouse nu face excepție:

select name
     , engine -- Используемый "движок" ("MergeTree", "Dictionary" etc)
     , ...
  from system.tables
 where database = 'HR'

Ceva similar se poate face în Cassandra (care are familii de coloane în loc de tabele și spații de chei în loc de scheme):

select columnfamily_name
     , compaction_strategy_class  -- Стратегия сборки мусора
     , gc_grace_seconds           -- Время жизни мусора
     , ...
  from system.schema_columnfamilies
 where keyspace_name = 'HR'

Pentru majoritatea celorlalte baze de date, puteți veni și cu interogări similare (chiar și Mongo are colecție specială de sistem, care conține informații despre toate colecțiile din sistem).

Desigur, în acest fel puteți obține informații nu numai despre tabele, ci și despre orice obiect în general. Din când în când, oameni amabili împărtășesc un astfel de cod pentru diferite baze de date, ca, de exemplu, în seria de articole habra „Funcții pentru documentarea bazelor de date PostgreSQL” (Ayb, Ben, Sală de gimnastică). Desigur, să păstrez tot acest munte de interogări în capul meu și să le tastez constant este o plăcere, așa că în IDE/editorul meu preferat am un set de fragmente pregătite în prealabil pentru interogările utilizate frecvent și tot ce rămâne este să tastați nume de obiecte în șablon.

Drept urmare, această metodă de navigare și căutare a obiectelor este mult mai flexibilă, economisește mult timp și vă permite să obțineți exact informațiile în forma în care este acum necesară (cum, de exemplu, este descrisă în postare). „Exportarea datelor dintr-o bază de date în orice format: ce pot face IDE-urile pe platforma IntelliJ”).

Operații cu obiecte

După ce am găsit și am studiat obiectele necesare, este timpul să facem ceva util cu ele. Desigur, și fără a-ți lua degetele de pe tastatură.

Nu este un secret că simpla ștergere a unui tabel va arăta la fel în aproape toate bazele de date:

drop table hr.persons

Dar odată cu crearea mesei devine mai interesant. Aproape orice DBMS (inclusiv multe NoSQL) poate „crea tabel” într-o formă sau alta, iar partea principală a acestuia va diferi chiar ușor (nume, listă de coloane, tipuri de date), dar alte detalii pot diferi dramatic și pot depinde de dispozitivul intern și capacitățile unui anumit SGBD. Exemplul meu preferat este că în documentația Oracle există doar BNF-uri „despuși” pentru sintaxa „create table” ocupa 31 de pagini. Alte SGBD-uri au capacități mai modeste, dar fiecare dintre ele are și multe caracteristici interesante și unice pentru crearea de tabele (Postgres, MySQL, gândac de bucătărie, Cassandra). Este puțin probabil ca vreun „vrăjitor” grafic dintr-un alt IDE (în special unul universal) să poată acoperi pe deplin toate aceste abilități și, chiar dacă va putea, nu va fi un spectacol pentru cei slabi de inimă. În același timp, o declarație scrisă corectă și în timp util creați un tabel vă va permite să le utilizați pe toate cu ușurință, să faceți stocarea și accesul la datele dvs. fiabile, optime și cât mai confortabile.

De asemenea, multe SGBD-uri au propriile lor tipuri specifice de obiecte care nu sunt disponibile în alte SGBD-uri. Mai mult, putem efectua operațiuni nu numai asupra obiectelor bazei de date, ci și asupra DBMS-ului însuși, de exemplu, „omorâm” un proces, eliberăm o zonă de memorie, activam urmărirea, trecem la modul „numai citire” și multe altele.

Acum hai să desenăm puțin

Una dintre cele mai comune sarcini este de a construi o diagramă cu obiecte de bază de date și de a vedea obiectele și conexiunile dintre ele într-o imagine frumoasă. Aproape orice IDE grafic, utilitare separate „linie de comandă”, instrumente grafice specializate și modelatori pot face acest lucru. Ei vor desena ceva pentru tine „cât de bine pot” și poți influența puțin acest proces doar cu ajutorul câtorva parametri din fișierul de configurare sau a casetelor de selectare din interfață.

Dar această problemă poate fi rezolvată mult mai simplu, mai flexibil și elegant, și bineînțeles cu ajutorul codului. Pentru a crea diagrame de orice complexitate, avem mai multe limbaje de marcare specializate (DOT, GraphML etc), și pentru ele o întreagă împrăștiere de aplicații (GraphViz, PlantUML, Mermaid) care pot citi astfel de instrucțiuni și le pot vizualiza într-o varietate de formate. . Ei bine, știm deja cum să obținem informații despre obiecte și conexiunile dintre ele.

Iată un mic exemplu despre cum ar putea arăta, folosind PlantUML și baza de date demonstrativă pentru PostgreSQL (în stânga este o interogare SQL care va genera instrucțiunea necesară pentru PlantUML, iar în dreapta este rezultatul):

Experiență „Baza de date ca cod”.

select '@startuml'||chr(10)||'hide methods'||chr(10)||'hide stereotypes' union all
select distinct ccu.table_name || ' --|> ' ||
       tc.table_name as val
  from table_constraints as tc
  join key_column_usage as kcu
    on tc.constraint_name = kcu.constraint_name
  join constraint_column_usage as ccu
    on ccu.constraint_name = tc.constraint_name
 where tc.constraint_type = 'FOREIGN KEY'
   and tc.table_name ~ '.*' union all
select '@enduml'

Și dacă încerci puțin, atunci bazează-te pe Șablon ER pentru PlantUML puteți obține ceva foarte asemănător cu o diagramă ER reală:

Interogarea SQL este puțin mai complicată

-- Шапка
select '@startuml
        !define Table(name,desc) class name as "desc" << (T,#FFAAAA) >>
        !define primary_key(x) <b>x</b>
        !define unique(x) <color:green>x</color>
        !define not_null(x) <u>x</u>
        hide methods
        hide stereotypes'
 union all
-- Таблицы
select format('Table(%s, "%s n information about %s") {'||chr(10), table_name, table_name, table_name) ||
       (select string_agg(column_name || ' ' || upper(udt_name), chr(10))
          from information_schema.columns
         where table_schema = 'public'
           and table_name = t.table_name) || chr(10) || '}'
  from information_schema.tables t
 where table_schema = 'public'
 union all
-- Связи между таблицами
select distinct ccu.table_name || ' "1" --> "0..N" ' || tc.table_name || format(' : "A %s may haven many %s"', ccu.table_name, tc.table_name)
  from information_schema.table_constraints as tc
  join information_schema.key_column_usage as kcu on tc.constraint_name = kcu.constraint_name
  join information_schema.constraint_column_usage as ccu on ccu.constraint_name = tc.constraint_name
 where tc.constraint_type = 'FOREIGN KEY'
   and ccu.constraint_schema = 'public'
   and tc.table_name ~ '.*'
 union all
-- Подвал
select '@enduml'

Experiență „Baza de date ca cod”.

Dacă te uiți cu atenție, sub capotă multe instrumente de vizualizare folosesc, de asemenea, interogări similare. Adevărat, aceste cereri sunt de obicei profunde „conectate” în codul aplicației în sine și sunt greu de înțeles, ca să nu mai vorbim de vreo modificare a acestora.

Metrici și monitorizare

Să trecem la un subiect tradițional complex - monitorizarea performanței bazei de date. Îmi amintesc o mică poveste adevărată spusă mie de „unul dintre prietenii mei”. Într-un alt proiect a trăit un anumit DBA puternic și puțini dintre dezvoltatori l-au cunoscut personal sau l-au văzut vreodată în persoană (în ciuda faptului că, conform zvonurilor, a lucrat undeva în clădirea alăturată) . La ora „X”, când sistemul de producție al unui mare retailer a început să „se simtă rău” din nou, el a trimis în tăcere capturi de ecran cu grafice de la Oracle Enterprise Manager, pe care a evidențiat cu atenție locurile critice cu un marcator roșu pentru „înțeles” ( asta, ca să spunem ușor, nu a ajutat prea mult). Și pe baza acestei „carte foto” a trebuit să tratez. În același timp, nimeni nu a avut acces la prețiosul (în ambele sensuri ale cuvântului) Enterprise Manager, deoarece sistemul este complex și costisitor, dintr-o dată „dezvoltatorii dau peste ceva și sparg totul”. Prin urmare, dezvoltatorii au găsit „empiric” locația și cauza frânelor și au lansat un patch. Dacă scrisoarea amenințătoare de la DBA nu ar sosi din nou în viitorul apropiat, atunci toată lumea ar putea răsufla ușurată și ar reveni la sarcinile lor curente (până la noua Scrisoare).

Dar procesul de monitorizare poate părea mai distractiv și prietenos și, cel mai important, accesibil și transparent pentru toată lumea. Cel puțin partea sa de bază, ca o completare la principalele sisteme de monitorizare (care sunt cu siguranță utile și în multe cazuri de neînlocuit). Orice SGBD este liber și absolut gratuit pentru a partaja informații despre starea și performanța sa actuală. În același „sângeros” Oracle DB, aproape orice informație despre performanță poate fi obținută din vizualizările sistemului, de la procese și sesiuni până la starea memoriei cache-ului tampon (de exemplu, Scripturi DBA, secțiunea „Monitorizare”). Postgresql are, de asemenea, o mulțime de vizualizări de sistem pentru monitorizarea bazei de date, în special cele care sunt indispensabile în viața de zi cu zi a oricărui DBA, precum pg_stat_activity, pg_stat_database, pg_stat_bgwriter. MySQL are chiar și o schemă separată pentru asta. schema_performanței. A În Mongo încorporat profiler agregă datele de performanță într-o colecție de sistem sistem.profil.

Astfel, înarmat cu un fel de colector de metrici (Telegraf, Metricbeat, Collectd) care poate efectua interogări SQL personalizate, o stocare a acestor metrici (InfluxDB, Elasticsearch, Timescaledb) și un vizualizator (Grafana, Kibana), puteți obține un instrument destul de ușor. și un sistem de monitorizare flexibil care va fi strâns integrat cu alte metrici la nivel de sistem (obținute, de exemplu, de la serverul de aplicații, de la sistemul de operare etc.). Ca, de exemplu, acest lucru se face în pgwatch2, care folosește combinația InfluxDB + Grafana și un set de interogări către vizualizările de sistem, care pot fi, de asemenea, accesate adăugați interogări personalizate.

În total

Și aceasta este doar o listă aproximativă a ceea ce se poate face cu baza noastră de date folosind cod SQL obișnuit. Sunt sigur că găsești multe alte utilizări, scrie în comentarii. Și vom vorbi despre cum (și cel mai important de ce) să automatizezi toate acestea și să le includem în conducta CI/CD data viitoare.

Sursa: www.habr.com

Adauga un comentariu