
SQL, vad kan vara enklare? Vi kan alla skriva en enkel frĂ„ga â vi skriver select, listar de nödvĂ€ndiga kolumnerna, sedan frĂ„n, tabellnamnet, lite villkor i dĂ€r och allt â dĂ„ har vi anvĂ€ndbara data i fickan, nĂ€stan oavsett vilken databas som ligger under huven (eller kanske inte ens Ă€r en databas) ). Som ett resultat kan arbetet med nĂ€stan vilken datakĂ€lla som helst (relationell eller inte) ses ur perspektivet av vanlig kod (med alla följder det innebĂ€r â versionskontroll, kodgranskning, statisk analys, automatiserade tester och allt det dĂ€r). Och detta gĂ€ller inte bara sjĂ€lva datan, scheman och migrationer, utan hela livscykeln för lagret. I denna artikel kommer vi att diskutera vardagliga uppgifter och problem med att arbeta med olika databaser med fokus pĂ„ "database as code".
Och vi börjar direkt med . De första striderna mellan "SQL vs ORM" sÄgs redan i .
Objekt-relationell mappning
FöresprÄkare av ORM vÀrderar traditionellt hastighet och enkelhet i utvecklingen, oberoende av databasen och renheten i koden. För mÄnga av oss ser koden för databasarbetet (och ofta sjÀlva databasen)
vanligtvis ut ungefÀr sÄ hÀr...
@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;
}
...Modellen Àr prydd med smarta annotationer, och nÄgonstans bakom kulisserna genererar och exekverar den ÄterhÄllsamma ORM:en tonvis med SQL-kod. För att sÀga sÄ, försöker utvecklarna med alla medel att skydda sig frÄn sin databas med kilometer av abstraktioner, vilket tyder pÄ en viss .
à andra sidan noterar föresprÄkarna för ren "handmade"-SQL möjligheten att pressa ut alla safter ur sin databas utan nÄgra extra lager och abstraktioner. Som ett resultat sÄ uppkommer "data-centric" projekt, dÀr databasen hanteras av sÀrskilt utbildade personer (de kallas ocksÄ "basister", "basoviker", "basdÀdjes" osv.), medan utvecklarna bara behöver "dra" fÀrdiga vyer och lagrade procedurer utan att bry sig om detaljerna.
Vad sÀgs om att ta det bÀsta frÄn tvÄ vÀrldar? Hur det görs i det fantastiska verktyget med det livsbejakande namnet . Jag kommer att ge ett par rader frÄn den övergripande konceptet i min fria översÀttning, och mer detaljerat kan man bekanta sig med det .
Clojure Àr ett fantastiskt sprÄk för att skapa DSL:er, men SQL Àr redan i sig ett imponerande DSL, och vi behöver inte ett till. S-expressioner Àr vackra, men hÀr tillför de inget nytt. I slutÀndan fÄr vi parenteser för parentesernas skull. HÄller ni inte med? DÄ fÄr ni vÀnta tills abstraktionen över databasen börjar lÀcka, och ni börjar brottas med funktionen. (raw-sql)
Och vad ska vi göra? LĂ„t oss behĂ„lla SQL som vanlig SQL â en fil för varje frĂ„ga:
-- namn: users-by-country
select *
from users
where country_code = :country_code⊠och lÀs sedan denna fil, och omvandla den till en vanlig Clojure-funktion:
(defqueries "some/where/users_by_country.sql"
{:connection db-spec})
;;; En funktion med namnet `users-by-country` har skapats.
;;; LÄt oss anvÀnda den:
(users-by-country {:country_code "GB"})
;=> ({:name "Kris" :country_code "GB" ...} ...)Genom att följa principen "SQL separat, Clojure separat", fÄr ni:
- Inga syntaxöverraskningar. Er databas (precis som vilken annan som helst) följer inte SQL-standarden till 100% â men för Yesql spelar det ingen roll. Ni kommer inte att slösa tid pĂ„ att jaga efter funktioner med syntax som Ă€r likvĂ€rdig med SQL. Ni kommer aldrig att behöva Ă„tervĂ€nda till funktionen. (raw-sql "some (âfunkyâ :: SYNTAX)")).
- BÀsta editorstöd. Er editor har redan utmÀrkt stöd för SQL. Genom att behÄlla SQL som SQL kan ni helt enkelt anvÀnda det.
- Kompatibilitet inom teamet. Era DBA:er kan lÀsa och skriva SQL som ni anvÀnder i ert Clojure-projekt.
- Enklare prestandaoptimering. Behöver ni skapa en plan för en problematisk frÄga? Inga problem nÀr er frÄga Àr vanlig SQL.
- Ă teranvĂ€ndning av frĂ„gor. Dra och slĂ€pp dessa samma SQL-filer till andra projekt, för det handlar bara om gammaldags SQL â dela dem bara.
Enligt mig Àr idén mycket cool och samtidigt mycket enkel, vilket har gett projektet mÄnga fördelar. pÄ olika sprÄk. Och vi kommer att försöka tillÀmpa en liknande filosofi att separera SQL-kod frÄn allt annat lÄngt bortom ORM.
IDE & DB-hanterare
Vi börjar med en enkel daglig uppgift. Ofta mÄste vi söka efter objekt i databasen, till exempel hitta en tabell i ett schema och studera dess struktur (vilka kolumner, nycklar, index, begrÀnsningar och sÄ vidare som anvÀnds). Och frÄn vilken grafisk IDE eller namnlig DB-manager som helst, förvÀntar vi oss först och frÀmst dessa förmÄgor. Det ska gÄ snabbt och vi vill inte vÀnta i en halv timme medan ett fönster med den nödvÀndiga informationen dras upp (sÀrskilt vid lÄngsamma anslutningar till en fjÀrrdatabas), och dessutom ska den information vi fÄr vara fÀrsk och aktuell, och inte gammal cache. Ju mer komplex och större databasen Àr, och ju fler databaser vi har, desto svÄrare Àr det att göra detta.
Men vanligtvis lÀgger jag musen Ät sidan och skriver bara kod. LÄt oss sÀga att vi behöver ta reda pÄ vilka tabeller (och med vilka egenskaper) som finns i schemat 'HR'. I de flesta DBMS kan vi fÄ det nödvÀndiga resultatet med följande enkla frÄga frÄn information_schema:
select table_name
, ...
from information_schema.tables
where schema = 'HR'InnehÄllet i sÄdana referenstabeller varierar frÄn databas till databas beroende pÄ kapaciteterna hos varje DBMS. Och till exempel för MySQL kan vi fÄ specifika parametrar för denna DBMS frÄn samma referens:
select table_name
, storage_engine -- AnvÀnd "motor" ("MyISAM", "InnoDB" etc)
, row_format -- Radformat ("Fixed", "Dynamic" etc)
, ...
from information_schema.tables
where schema = 'HR'Oracle kan inte information_schema, men det har , och det uppstÄr inga stora problem:
select table_name
, pct_free -- Minimal ledig plats i datakluster (%)
, pct_used -- Minimal anvÀnd plats i datakluster (%)
, last_analyzed -- Datum för senaste statistikinsamlingen
, ...
from all_tables
where owner = 'HR'ClickHouse Àr inget undantag:
select name
, engine -- AnvÀnd "motor" ("MergeTree", "Dictionary" etc)
, ...
from system.tables
where database = 'HR'NÄgot liknande kan göras i Cassandra (dÀr det finns columnfamilies istÀllet för tabeller och keyspaces istÀllet för scheman):
select columnfamily_name
, compaction_strategy_class -- SkrÀputtagningsstrategi
, gc_grace_seconds -- SkrÀpets livslÀngd
, ...
from system.schema_columnfamilies
where keyspace_name = 'HR'För de flesta andra databaser kan liknande frÄgor ocksÄ konstrueras (Àven i Mongo finns det , som innehÄller information om alla kollektioner i systemet).
SjÀlvklart kan man pÄ detta sÀtt fÄ information inte bara om tabeller, utan om vilket objekt som helst. DÄ och dÄ delar snÀlla mÀnniskor med sig av sÄdan kod för olika databaser, som till exempel i serien av Habra-artiklar "Funktioner för dokumentering av PostgreSQL-databaser" (, , ). SjÀlvklart Àr det "sÄdÀr" roligt att hÄlla hela denna mÀngd frÄgor i huvudet och stÀndigt skriva in dem, sÄ i min favorit-IDE/redigerare har jag ett förberett set av snippar för ofta anvÀnda frÄgor, och det Àr bara att fylla i objektnamnen i mallen.
Som ett resultat Àr ett sÄdant sÀtt att navigera och söka efter objekt mycket mer flexibelt, sparar mycket tid, och gör det möjligt att fÄ just den informationen i det format som Àr nödvÀndigt just nu (som beskrivs i inlÀgget ).
Operationer med objekt
Efter att vi har hittat och studerat de nödvÀndiga objekten Àr det dags att göra nÄgot nyttigt med dem. SjÀlvklart utan att ta fingrarna frÄn tangentbordet.
Det Àr ingen hemlighet att enkel borttagning av en tabell kommer att se likadan ut nÀstan i alla databaser:
drop table hr.personsMen att skapa en tabell Àr redan mer intressant. NÀstan varje DBMS (inklusive mÄnga NoSQL) kan pÄ ett eller annat sÀtt "create table", och huvuddelen kommer knappt att skilja sig (namn, kolumnlista, datatyper), men andra detaljer kan skilja sig kraftigt och beror pÄ den interna strukturen och möjligheterna hos den specifika DBMS. Mitt favoritexempel Àr att i Oracle-dokumentationen tar bara "rÄa" BNF:er för syftet "create table" . Andra DBMS har mer blygsamma möjligheter, men var och en av dem har ocksÄ mÄnga intressanta och unika funktioner för att skapa tabeller (, , , ). Det Àr osannolikt att nÄgon grafisk "wizard" frÄn nÀsta IDE (sÀrskilt en allmÀn) helt kan tÀcka alla dessa funktioner, och om den skulle kunna, sÄ skulle det vara en scen som inte Àr för de med svaga nerver. Samtidigt kommer en korrekt och i rÀtt tid skriven operator create table att möjliggöra att utan svÄrighet utnyttja alla dessa, göra lagring och tillgÄng till dina data pÄlitlig, optimal och sÄ bekvÀm som möjligt.
MÄnga databashanterare har sina specifika objekttyper som saknas i andra databashanterare. Vi kan dessutom utföra operationer inte bara pÄ databasobjekten, utan Àven pÄ sjÀlva databashanteraren, som att "döda" en process, frigöra ett minnesomrÄde, aktivera spÄrning, gÄ in i "read only"-lÀge och mycket mer.
Och nu ska vi göra lite teckningar
En av de vanligaste uppgifterna Àr att skapa ett diagram med databasobjekten, för att pÄ en vacker bild se objekt och relationer mellan dem. NÀstan varje grafisk IDE, vissa kommandoradsverktyg, specialiserade grafiska verktyg och modellerare kan göra detta. De kommer att skapa en bild "som de kan", och endast genom att justera ett par parametrar i konfigurationsfilen eller kryssrutor i grÀnssnittet kan man pÄverka denna process.
Men detta problem kan lösas pÄ ett mycket enklare, mer flexibelt och elegant sÀtt, naturligtvis med hjÀlp av kod. För att skapa diagram av vilken komplexitet som helst har vi flera specialiserade mÀrksprÄk (DOT, GraphML, etc.), och med dem en hel del program (GraphViz, PlantUML, Mermaid) som kan lÀsa sÄdana instruktioner och visualisera i olika format. Vi vet redan hur man fÄr information om objekten och relationerna mellan dem.
LÄt oss ge ett litet exempel pÄ hur det skulle kunna se ut, med hjÀlp av PlantUML och (till vÀnster SQL-frÄgan som genererar den nödvÀndiga instruktionen för PlantUML, och till höger resultatet):

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'Om man anstrÀnger sig lite kan man pÄ basis av fÄ nÄgot som liknar en riktig ER-diagram:
SQL-frÄgan Àr lite mer komplicerad
-- Huvud
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
-- Tabeller
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
-- Relationer mellan tabeller
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
-- Sidfot
select '@enduml' 
Om man tittar noga sÄ anvÀnder de flesta visualiseringsverktyg pÄ samma sÀtt liknande frÄgor. Men dessa frÄgor Àr vanligtvis djupt , för att inte tala om att modifiera dem.
MÀtningar och övervakning
LĂ„t oss gĂ„ vidare till ett traditionellt komplext Ă€mne â övervakning av databashĂ€lsa. Jag kommer ihĂ„g en liten sann historia som "en av mina vĂ€nner" berĂ€ttade för mig. I ett pĂ„gĂ„ende projekt fanns det en mĂ€ktig DBA, och fĂ„ av utvecklarna kĂ€nde honom personligen, eller hade överhuvudtaget sett honom (trots att han, enligt rykten, arbetade nĂ„gonstans i det angrĂ€nsande huset). Vid tidpunkten "X", nĂ€r produktionssystemet för en stor detaljhandlare Ă„terigen började "mĂ„ dĂ„ligt", skickade han tyst skĂ€rmdumpar av diagram frĂ„n Oracle Enterprise Manager, dĂ€r han noggrant markerade kritiska punkter med röd penna för "tydlighet" (vilket, milt sagt, sĂ€llan hjĂ€lpte). Och det var pĂ„ denna "bild" man fick basera sina Ă„tgĂ€rder. Men ingen hade tillgĂ„ng till det vĂ€rdefulla (i bĂ„da bemĂ€rkelser) Enterprise Manager, eftersom systemet Ă€r komplext och dyrt, och vem vet om "utvecklarna stöter pĂ„ nĂ„got och förstör allt". DĂ€rför hittade utvecklarna "empiriskt" platsen och orsaken till fördröjningar och slĂ€ppte en patch. Om det skrĂ€mmande brevet frĂ„n DBA inte kom igen inom den nĂ€rmaste tiden, kĂ€nde alla en lĂ€ttnad och Ă„tervĂ€nde till sina pĂ„gĂ„ende uppgifter (tills ett nytt brev kom in).
Men övervakningsprocessen kan se betydligt mer underhĂ„llande och vĂ€nlig ut, och framför allt â tillgĂ€nglig och transparent för alla. Ă tminstone dess grundlĂ€ggande del, som ett komplement till de viktiga övervakningssystemen (som utan tvekan Ă€r anvĂ€ndbara och i mĂ„nga fall oersĂ€ttliga). Varje DBMS Ă€r fritt och helt utan kostnad redo att dela med sig av information om sitt nuvarande tillstĂ„nd och prestanda. I den "blodiga" Oracle DB kan man nĂ€stan fĂ„ all information om prestanda frĂ„n systemvisningar, allt frĂ„n processer och sessioner till tillstĂ„ndet för buffertcachen (till exempel, , avsnittet "Ăvervakning"). I PostgreSQL finns det ocksĂ„ en rad systemvisningar för , sĂ€rskilt sĂ„dana som Ă€r oumbĂ€rliga i det dagliga livet för varje DBA, som , , . I MySQL Ă€r det till och med en separat skema som Ă€r avsedd för detta, . Och i Mongo finns det en inbyggd som samlar in prestandadata i en systemkollektion .
SÄledes, med hjÀlp av en metrics-aggregator (Telegraf, Metricbeat, Collectd) som kan utföra skrÀddarsydda SQL-frÄgor, en databas för dessa metrics (InfluxDB, Elasticsearch, Timescaledb) och en visualiserare (Grafana, Kibana), kan man fÄ ett relativt enkelt och flexibelt övervakningssystem som kommer att vara nÀra integrerat med andra systemmetrics (hÀmtade, till exempel, frÄn applikationsservrar, operativsystem osv.). Som detta, till exempel, görs i pgwatch2, dÀr en kombination av InfluxDB + Grafana och en uppsÀttning frÄgor mot systemvyer anvÀnds, till vilka man ocksÄ kan .
Sammanfattningsvis
Och detta Àr bara en preliminÀr lista över vad man kan göra med vÄr databas genom vanlig SQL-kod. Jag Àr sÀker pÄ att det finns mÄnga fler tillÀmpningar, skriv gÀrna i kommentarerna. NÀsta gÄng ska vi prata om hur (och viktigast av allt varför) man automatiserar detta och integrerar det i sin CI/CD-pipeline.
KĂ€lla: habr.com
