Izkušnja "Baza podatkov kot koda".

Izkušnja "Baza podatkov kot koda".

SQL, kaj bi lahko bilo preprostejšega? Vsak od nas lahko napiše preprosto zahtevo - vtipkamo izberite, nato navedite zahtevane stolpce iz, ime tabele, nekateri pogoji v Kje in to je vse - uporabni podatki so v našem žepu in (skoraj) ne glede na to, kateri DBMS je takrat pod pokrovom (ali morda sploh ni DBMS). Posledično je delo s skoraj vsemi viri podatkov (relacijskimi in ne tako) mogoče obravnavati z vidika običajne kode (z vsem, kar pomeni - nadzor različic, pregled kode, statična analiza, samodejni testi in to je vse). In to ne velja le za same podatke, sheme in migracije, ampak na splošno za celotno življenjsko dobo shrambe. V tem članku bomo govorili o vsakodnevnih opravilih in problemih dela z različnimi bazami podatkov pod optiko »baze podatkov kot kode«.

In začnimo takoj od ORM. Prve bitke tipa "SQL proti ORM" smo opazili že l predpetrovska Rusija.

Objektno-relacijsko preslikavo

Podporniki ORM tradicionalno cenijo hitrost in enostavnost razvoja, neodvisnost od DBMS in čisto kodo. Za mnoge od nas je koda za delo z bazo podatkov (in pogosto sama baza podatkov)

ponavadi zgleda nekako tako...

@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;
    }
  ...

Model je obešen s pametnimi opombami, nekje v zakulisju pa pogumen ORM generira in izvaja ogromno kode SQL. Mimogrede, razvijalci se po svojih najboljših močeh trudijo izolirati od svoje podatkovne baze s kilometri abstrakcij, kar kaže na nekaj "sovražim SQL".

Na drugi strani barikad privrženci čistega "ročno izdelanega" SQL opažajo zmožnost iztisniti ves sok iz svojega DBMS brez dodatnih plasti in abstrakcij. Posledično se pojavljajo »data-centric« projekti, kjer so v bazo vključeni posebej usposobljeni ljudje (so tudi »bazičarji«, so tudi »bazičarji«, so tudi »basdenerji« itd.) in razvijalci »potegniti« je treba le že pripravljene poglede in shranjene procedure, ne da bi se spuščali v podrobnosti.

Kaj če bi imeli najboljše iz obeh svetov? Kako je to storjeno v čudovitem orodju z življenjskim imenom Yesql. V svojem brezplačnem prevodu bom podal nekaj vrstic iz splošnega koncepta, vi pa se lahko podrobneje seznanite z njim tukaj.

Clojure je kul jezik za ustvarjanje DSL-jev, vendar je SQL sam kul DSL in ne potrebujemo drugega. S-izrazi so super, vendar tu ne dodajo nič novega. Kot rezultat, dobimo oklepaje zaradi oklepajev. Se ne strinjate? Nato počakajte na trenutek, ko začne abstrakcija nad bazo uhajati in se začnete boriti s funkcijo (raw-sql)

Torej, kaj naj storim? Pustimo SQL kot običajni SQL - ena datoteka na zahtevo:

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

... in nato preberite to datoteko in jo spremenite v običajno funkcijo 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" ...} ...)

Z upoštevanjem načela "SQL sam po sebi, Clojure sam po sebi" dobite:

  • Brez sintaktičnih presenečenj. Vaša zbirka podatkov (tako kot katera koli druga) ni 100% skladna s standardom SQL - vendar to za Yesql ni pomembno. Nikoli ne boste izgubljali časa z iskanjem funkcij s sintakso, enakovredno SQL. Nikoli se vam ne bo treba vrniti na funkcijo (raw-sql "some('funky'::SYNTAX)")).
  • Najboljša podpora za urejevalnike. Vaš urejevalnik že ima odlično podporo za SQL. Če shranite SQL kot SQL, ga lahko preprosto uporabite.
  • Združljivost ekipe. Vaši skrbniki baze podatkov lahko berejo in pišejo SQL, ki ga uporabljate v svojem projektu Clojure.
  • Lažje prilagajanje zmogljivosti. Potrebujete načrt za problematično poizvedbo? To ni težava, če je vaša poizvedba običajni SQL.
  • Ponovna uporaba poizvedb. Te iste datoteke SQL povlecite in spustite v druge projekte, ker gre za navaden stari SQL – preprosto ga delite.

Po mojem mnenju je ideja zelo kul in hkrati zelo preprosta, zahvaljujoč kateri je projekt pridobil veliko ljudi sledilci v različnih jezikih. In naslednjič bomo poskušali uporabiti podobno filozofijo ločevanja kode SQL od vsega drugega, kar je daleč onkraj ORM.

Upravitelji IDE & DB

Začnimo s preprostim vsakodnevnim opravilom. Pogosto moramo iskati nekatere predmete v bazi podatkov, na primer najti tabelo v shemi in preučiti njeno strukturo (kateri stolpci, ključi, indeksi, omejitve itd. so uporabljeni). In od katerega koli grafičnega IDE ali majhnega upravitelja DB najprej pričakujemo točno te sposobnosti. Da je hitro in vam ni treba čakati pol ure, da se izriše okno s potrebnimi informacijami (še posebej pri počasni povezavi z oddaljeno bazo podatkov), hkrati pa so prejete informacije sveže in relevantne, in ne predpomnjene smeti. Še več, bolj kompleksna in večja kot je baza podatkov ter večje kot jih je, težje je to narediti.

Ampak ponavadi vržem miško stran in samo napišem kodo. Recimo, da morate ugotoviti, katere tabele (in s katerimi lastnostmi) so vsebovane v shemi "HR". V večini DBMS je mogoče želeni rezultat doseči s to preprosto poizvedbo iz information_schema:

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

Od baze do baze podatkov se vsebina takšnih referenčnih tabel razlikuje glede na zmogljivost posamezne DBMS. In na primer za MySQL lahko iz iste referenčne knjige dobite parametre tabele, specifične za to DBMS:

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

Oracle ne pozna informacijske_sheme, vendar jo pozna Oracle metapodatkiin ni velikih težav:

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

ClickHouse ni izjema:

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

Nekaj ​​podobnega je mogoče narediti v Cassandri (ki ima družine stolpcev namesto tabel in prostore ključev namesto shem):

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

Za večino drugih baz podatkov lahko dobite tudi podobne poizvedbe (celo Mongo ima zbiranje posebnega sistema, ki vsebuje podatke o vseh zbirkah v sistemu).

Seveda lahko na ta način dobite informacije ne samo o tabelah, ampak o katerem koli predmetu na splošno. Od časa do časa prijazni ljudje delijo takšno kodo za različne baze podatkov, kot je na primer v seriji člankov habra "Funkcije za dokumentiranje baz podatkov PostgreSQL" (Ayb, Ben, telovadnica). Seveda mi je obdržati to goro poizvedb v glavi in ​​jih neprestano tipkati v tak užitek, tako da imam v svojem najljubšem IDE/urejevalniku vnaprej pripravljen nabor izrezkov za pogosto uporabljene poizvedbe in vse, kar mi preostane, je vtipkati imena predmetov v predlogo.

Posledično je ta način navigacije in iskanja predmetov veliko bolj prilagodljiv, prihrani veliko časa in vam omogoča, da dobite točno tiste informacije v obliki, v kateri so zdaj potrebne (kot je na primer opisano v objavi "Izvoz podatkov iz baze podatkov v poljubnem formatu: kaj zmorejo IDE na platformi IntelliJ").

Operacije s predmeti

Ko smo našli in preučili potrebne predmete, je čas, da z njimi naredimo nekaj koristnega. Seveda tudi brez odmika prstov s tipkovnice.

Ni skrivnost, da bo preprosto brisanje tabele videti enako v skoraj vseh zbirkah podatkov:

drop table hr.persons

Toda z ustvarjanjem mize postane bolj zanimivo. Skoraj vsak DBMS (vključno s številnimi NoSQL) lahko "ustvari tabelo" v takšni ali drugačni obliki, njen glavni del pa se bo celo nekoliko razlikoval (ime, seznam stolpcev, tipi podatkov), druge podrobnosti pa se lahko dramatično razlikujejo in so odvisne od notranja naprava in zmogljivosti določenega DBMS. Moj najljubši primer je, da so v Oraclovi dokumentaciji samo »goli« BNF-ji za sintakso »ustvari tabelo«. obsega 31 strani. Drugi DBMS imajo skromnejše zmogljivosti, vendar ima vsak od njih tudi veliko zanimivih in edinstvenih funkcij za ustvarjanje tabel (postgres, mysql, ščurka, cassandra). Malo verjetno je, da bo kateri koli grafični "čarovnik" iz drugega IDE (zlasti univerzalnega) lahko v celoti pokril vse te zmožnosti, in tudi če bi lahko, to ne bo spektakel za ljudi s slabim srcem. Hkrati pa pravilno in pravočasno napisano izjavo ustvarite tabelo vam bo omogočil enostavno uporabo vseh, poskrbel za zanesljivo, optimalno in čim bolj udobno shranjevanje in dostop do vaših podatkov.

Prav tako imajo številni DBMS-ji svoje posebne vrste objektov, ki niso na voljo v drugih DBMS-jih. Poleg tega lahko izvajamo operacije ne samo na objektih baze podatkov, ampak tudi na samem DBMS, na primer "ubijemo" proces, sprostimo nekaj pomnilniškega območja, omogočimo sledenje, preklopimo v način "samo za branje" in še veliko več.

Zdaj pa malo narišimo

Ena najpogostejših nalog je zgraditi diagram s predmeti baze podatkov in videti objekte in povezave med njimi v čudoviti sliki. To zmorejo skoraj vsi grafični IDE, ločeni pripomočki »ukazne vrstice«, specializirana grafična orodja in modelirji. Nekaj ​​vam bodo narisali »po najboljših močeh«, na ta proces pa lahko nekoliko vplivate le s pomočjo nekaj parametrov v konfiguracijski datoteki ali potrditvenih polj v vmesniku.

Toda to težavo je mogoče rešiti veliko preprosteje, bolj prilagodljivo in elegantno ter seveda s pomočjo kode. Za ustvarjanje diagramov kakršne koli kompleksnosti imamo več specializiranih označevalnih jezikov (DOT, GraphML itd.) in zanje celo vrsto aplikacij (GraphViz, PlantUML, Mermaid), ki lahko berejo takšna navodila in jih vizualizirajo v različnih formatih . No, informacije o predmetih in povezavah med njimi že vemo.

Tukaj je majhen primer, kako bi to lahko izgledalo z uporabo PlantUML in predstavitvena zbirka podatkov za PostgreSQL (na levi je poizvedba SQL, ki bo ustvarila zahtevana navodila za PlantUML, na desni pa je rezultat):

Izkušnja "Baza podatkov kot koda".

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'

In če malo poskusite, potem na podlagi Predloga ER za PlantUML lahko dobite nekaj zelo podobnega pravemu ER diagramu:

Poizvedba SQL je nekoliko bolj zapletena

-- Шапка
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'

Izkušnja "Baza podatkov kot koda".

Če natančno pogledate, pod pokrovom veliko orodij za vizualizacijo prav tako uporablja podobne poizvedbe. Res je, te zahteve so običajno globoke »vgrajeni« v kodo same aplikacije in jih je težko razumeti, da ne omenjam njihovega spreminjanja.

Meritve in spremljanje

Preidimo na tradicionalno zapleteno temo - spremljanje delovanja baze podatkov. Spomnim se majhne resnične zgodbe, ki mi jo je povedal "eden od mojih prijateljev". Na drugem projektu je živel določen močan DBA in le malo razvijalcev ga je osebno poznalo ali ga je kdaj videlo osebno (kljub dejstvu, da je po govoricah delal nekje v sosednji stavbi). Ob uri »X«, ko je produkcijskemu sistemu velikega trgovca znova »slabo«, je tiho poslal posnetke zaslona grafov iz Oracle Enterprise Manager, na katerih je za »razumljivost« z rdečim flomastrom skrbno poudaril kritična mesta ( to, milo rečeno, ni veliko pomagalo). In na podlagi te "foto karte" sem moral zdraviti. Hkrati nihče ni imel dostopa do dragocenega (v obeh pomenih besede) Enterprise Managerja, ker sistem je zapleten in drag, nenadoma se »razvijalci ob nekaj spotaknejo in vse pokvarijo«. Zato so razvijalci »empirično« našli lokacijo in vzrok zavor ter izdali popravek. Če grozeče pismo DBA ne bi v kratkem spet prispelo, potem bi si vsi oddahnili in se vrnili k svojim trenutnim nalogam (do novega Pisma).

Toda proces spremljanja je lahko videti bolj zabaven in prijazen, predvsem pa dostopen in pregleden za vse. Vsaj njegov osnovni del, kot dodatek k glavnim nadzornim sistemom (ki so vsekakor uporabni in v mnogih primerih nenadomestljivi). Vsak DBMS lahko svobodno in popolnoma brezplačno deli informacije o svojem trenutnem stanju in delovanju. V istem "krvavem" Oracle DB je skoraj vse informacije o zmogljivosti mogoče dobiti iz sistemskih pogledov, od procesov in sej do stanja medpomnilnika (npr. Skripte DBA, razdelek "Spremljanje"). Postgresql ima tudi cel kup sistemskih pogledov za spremljanje baze podatkov, še posebej tistih, ki so nepogrešljivi v vsakdanjem življenju vsakega DBA, kot npr pg_stat_activity, pg_stat_baza podatkov, pg_stat_bgwriter. MySQL ima celo ločeno shemo za to. shema_izvedbe. A V Mongo vgrajen profiler združuje podatke o zmogljivosti v sistemsko zbirko system.profile.

Torej, oboroženi z nekakšnim zbiralnikom metrik (Telegraf, Metricbeat, Collectd), ki lahko izvaja poizvedbe sql po meri, shrambo teh metrik (InfluxDB, Elasticsearch, Timescaledb) in vizualizatorjem (Grafana, Kibana), lahko dobite dokaj enostavno in prilagodljiv nadzorni sistem, ki bo tesno povezan z drugimi metrikami celotnega sistema (pridobljenimi na primer iz aplikacijskega strežnika, iz operacijskega sistema itd.). Kot je na primer to storjeno v pgwatch2, ki uporablja kombinacijo InfluxDB + Grafana in nabor poizvedb do sistemskih pogledov, do katerih lahko tudi dostopate dodajte poizvedbe po meri.

Skupno

In to je le približen seznam tega, kar je mogoče storiti z našo bazo podatkov z uporabo običajne kode SQL. Prepričan sem, da lahko najdete veliko več uporab, zapišite v komentarje. In govorili bomo o tem, kako (in kar je najpomembneje, zakaj) vse to avtomatizirati in naslednjič vključiti v svoj CI/CD cevovod.

Vir: www.habr.com

Dodaj komentar