{"id":83649,"date":"2020-06-02T07:42:21","date_gmt":"2020-06-02T05:42:21","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/database-as-sode-experience"},"modified":"2020-06-02T07:42:21","modified_gmt":"2020-06-02T05:42:21","slug":"database-as-sode-experience","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/database-as-sode-experience","title":{"rendered":"Do\u015bwiadczenie \u00abDatabase as Code\u00bb","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Do\u015bwiadczenie \u00abDatabase as Code\u00bb\" src=\"\/wp-content\/uploads\/2020\/06\/a1ac23deeded97559fbe787ed09184a5.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>SQL, co mo\u017ce by\u0107 prostszego? Ka\u017cdy z nas mo\u017ce napisa\u0107 prosty zapytanie \u2014 wpisujemy <strong><em>select<\/em><\/strong>, wymieniamy potrzebne kolumny, potem <strong><em>od<\/em><\/strong>, nazw\u0119 tabeli, troch\u0119 warunk\u00f3w w <strong><em>gdzie<\/em><\/strong> i to wszystko \u2014 przydatne dane mamy w kieszeni, niezale\u017cnie od tego, jaka baza danych jest aktualnie u\u017cywana (a mo\u017ce i <noindex><a rel=\"nofollow\" href=\"https:\/\/osquery.io\/\">wcale nie baza danych<\/a><\/noindex>). W rezultacie, prac\u0119 z praktycznie ka\u017cdym \u017ar\u00f3d\u0142em danych (relacyjnym i nie tylko) mo\u017cna rozpatrywa\u0107 z perspektywy zwyk\u0142ego kodu (ze wszystkimi tego konsekwencjami \u2014 kontrola wersji, przegl\u0105d kodu, analiza statyczna, testy automatyczne i tym podobne). Dotyczy to nie tylko samych danych, schemat\u00f3w i migracji, ale ca\u0142ej dzia\u0142alno\u015bci magazynu. W tym artykule om\u00f3wimy codzienne zadania i problemy zwi\u0105zane z r\u00f3\u017cnymi bazami danych w kontek\u015bcie \u201ebazy danych jako kodu\u201d.<\/p>\n<p><\/p>\n<p>Zaczniemy od <noindex><a rel=\"nofollow\" href=\"https:\/\/www.yegor256.com\/2014\/12\/01\/orm-offensive-anti-pattern.html\">ORM<\/a><\/noindex>. Pierwsze bitwy typu \u201eSQL kontra ORM\u201d zauwa\u017cono ju\u017c w <noindex><a rel=\"nofollow\" href=\"https:\/\/www.sql.ru\/forum\/904343\/orm-vs-sql\">przedpotopowej Rosji<\/a><\/noindex>.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h5 id=\"obektno-relyacionnyy-maping\">Mapowanie obiektowo-relacyjne<\/h5>\n<p><\/p>\n<p>Zwolennicy ORM tradycyjnie ceni\u0105 szybko\u015b\u0107 i prostot\u0119 tworzenia, niezale\u017cno\u015b\u0107 od bazy danych oraz czysto\u015b\u0107 kodu. Dla wielu z nas kod pracy z baz\u0105 danych (a cz\u0119sto i sama baza danych)<\/p>\n<p><\/p>\n<p>                        <b class=\"spoiler_title\">zwykle wygl\u0105da to mniej wi\u0119cej tak\u2026<\/b><\/p>\n<pre><code class=\"java\">@Entity\n@Table(name = \"stock\", catalog = \"maindb\", uniqueConstraints = {\n        @UniqueConstraint(columnNames = \"STOCK_NAME\"),\n        @UniqueConstraint(columnNames = \"STOCK_CODE\") })\npublic class Stock implements java.io.Serializable {\n\n    @Id\n    @GeneratedValue(strategy = IDENTITY)\n    @Column(name = \"STOCK_ID\", unique = true, nullable = false)\n    public Integer getStockId() {\n        return this.stockId;\n    }\n  ...<\/code><\/pre>\n<p><\/p>\n<p>Model jest obci\u0105\u017cony m\u0105drymi adnotacjami, a gdzie\u015b w tle dzielny ORM generuje i wykonuje tony jakiego\u015b kodu SQL. Swoj\u0105 drog\u0105, programi\u015bci za wszelk\u0105 cen\u0119 staraj\u0105 si\u0119 oddzieli\u0107 swoje bazy danych od siebie, tworz\u0105c kilometry abstrakcji, co \u015bwiadczy o pewnej <noindex><a rel=\"nofollow\" href=\"https:\/\/www.sql.ru\/forum\/1303231\/prichiny-nenavisti-k-yazyku-sql\">\"Nienawi\u015b\u0107 do SQL\"<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>Po drugiej stronie barykady zwolennicy czystego \"handmade\"-SQL podkre\u015blaj\u0105 mo\u017cliwo\u015b\u0107 wydobycia maksimum z w\u0142asnej bazy danych bez dodatkowych warstw i abstrakcji. W efekcie powstaj\u0105 projekty \u201edata-centric\u201d, w kt\u00f3rych baz\u0105 zajmuj\u0105 si\u0119 specjalnie przeszkolone osoby (zwane \u201ebazistami\u201d, \u201ebazowikami\u201d, \u201ebazd\u0119nikami\u201d itd.), a programi\u015bci tylko \u201ewyci\u0105gaj\u0105\u201d gotowe widoki i procedury, nie wnikaj\u0105c w szczeg\u00f3\u0142y.<\/p>\n<p><\/p>\n<p>A co je\u015bli we\u017amiemy to, co najlepsze z dw\u00f3ch \u015bwiat\u00f3w? Jak to jest zrealizowane w wspania\u0142ym narz\u0119dziu o afirmatywnym tytule <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/krisajenkins\/yesql\">Yesql<\/a><\/noindex>. Przytocz\u0119 kilka zda\u0144 z og\u00f3lnej koncepcji w moim swobodnym t\u0142umaczeniu, a bardziej szczeg\u00f3\u0142owo z ni\u0105 mo\u017cna si\u0119 zapozna\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/krisajenkins\/yesql#rationale\">tutaj<\/a><\/noindex>.<\/p>\n<p><\/p>\n<blockquote><p>Clojure to \u015bwietny j\u0119zyk do tworzenia DSL, jednak SQL sam w sobie jest ju\u017c \u015bwietnym DSL, i nie potrzebujemy kolejnego. Wyra\u017cenia S s\u0105 pi\u0119kne, ale tutaj nie wnosz\u0105 nic nowego. Ostatecznie otrzymujemy nawiasy dla nawias\u00f3w. Nie zgadzasz si\u0119? W takim razie poczekaj, a\u017c abstrahacja nad baz\u0105 danych zacznie przecieka\u0107, a ty zaczniesz walk\u0119 z funkcj\u0105. <em>(raw-sql)<\/em><\/p>\n<p>I co robi\u0107? Zostawmy SQL jako zwyk\u0142y SQL \u2014 jeden plik na jeden zapytanie:<\/p><\/blockquote>\n<p><\/p>\n<pre><code class=\"sql\">-- name: users-by-country\nselect *\n  from users\n where country_code = :country_code<\/code><\/pre>\n<p><\/p>\n<blockquote><p>\u2026 a nast\u0119pnie przeczytaj ten plik, przekszta\u0142caj\u0105c go w zwyk\u0142\u0105 funkcj\u0119 Clojure:<\/p><\/blockquote>\n<p><\/p>\n<pre><code class=\"lisp\">(defqueries \"some\/where\/users_by_country.sql\"\n   {:connection db-spec})\n\n;;; Stworzono funkcj\u0119 o nazwie `users-by-country`.\n;;; U\u017cyjmy jej:\n(users-by-country {:country_code \"GB\"})\n;=&gt; ({:name \"Kris\" :country_code \"GB\" ...} ...)<\/code><\/pre>\n<p><\/p>\n<blockquote><p>Przestrzegaj\u0105c zasady \"SQL osobno, Clojure osobno\", otrzymujesz:<\/p>\n<ul>\n<li>Brak zaskocze\u0144 sk\u0142adniowych. Twoja baza danych (jak ka\u017cda inna) nie odpowiada standardowi SQL w 100% \u2014 ale dla Yesql to nie ma znaczenia. Nigdy nie b\u0119dziesz traci\u0107 czasu na polowanie na funkcje o sk\u0142adni odpowiadaj\u0105cej SQL. Nigdy nie b\u0119dziesz musia\u0142 wraca\u0107 do funkcji. <em>(raw-sql \"some (\u2018funky\u2019 :: SYNTAX)\")<\/em>.<\/li>\n<li>Najlepsze wsparcie edytora. Tw\u00f3j edytor ma ju\u017c doskona\u0142e wsparcie dla SQL. Utrzymuj\u0105c SQL jako SQL, mo\u017cesz po prostu go u\u017cywa\u0107.<\/li>\n<li>Zgodno\u015b\u0107 zespo\u0142owa. Twoi DBA mog\u0105 czyta\u0107 i pisa\u0107 SQL, kt\u00f3rego u\u017cywasz w swoim projekcie Clojure.<\/li>\n<li>\u0141atwiejsza konfiguracja wydajno\u015bci. Musisz zbudowa\u0107 plan dla problematycznego zapytania? To nie problem, gdy Twoje zapytanie jest zwyk\u0142ym SQL.<\/li>\n<li>Ponowne u\u017cycie zapyta\u0144. Przenie\u015b te same pliki SQL do innych projekt\u00f3w, poniewa\u017c to po prostu stary dobry SQL \u2014 po prostu podziel si\u0119 nim.<\/li>\n<\/ul>\n<p>\n<\/p><\/blockquote>\n<p>Moim zdaniem pomys\u0142 jest bardzo fajny, a przy tym bardzo prosty, dzi\u0119ki czemu projekt zyska\u0142 wiele. <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/krisajenkins\/yesql#other-languages\">na\u015bladowc\u00f3w<\/a><\/noindex> w r\u00f3\u017cnych j\u0119zykach. A my nast\u0119pnie spr\u00f3bujemy zastosowa\u0107 podobn\u0105 filozofi\u0119 oddzielania kodu SQL od reszty dumnie poza ORM.<\/p>\n<p><\/p>\n<h5 id=\"ide--db-menedzhery\">IDE &amp; DB-menad\u017cery<\/h5>\n<p><\/p>\n<p>Zacznijmy od prostego zadania codziennego. Cz\u0119sto musimy wyszukiwa\u0107 r\u00f3\u017cne obiekty w bazie danych, na przyk\u0142ad znale\u017a\u0107 tabel\u0119 w schemacie i zbada\u0107 jej struktur\u0119 (jakie kolumny, klucze, indeksy, ograniczenia i inne s\u0105 u\u017cywane). I od ka\u017cdej graficznej IDE lub jakiegokolwiek mened\u017cera DB oczekujemy przede wszystkim takich mo\u017cliwo\u015bci. Musi by\u0107 szybko, aby nie czeka\u0107 p\u00f3\u0142 godziny, a\u017c otworzy si\u0119 okno z potrzebnymi informacjami (szczeg\u00f3lnie przy wolnym po\u0142\u0105czeniu z zdaln\u0105 baz\u0105 danych), a otrzymane informacje musz\u0105 by\u0107 \u015bwie\u017ce i aktualne, a nie stary, zbuforowany materia\u0142. Im bardziej skomplikowana i obszerna baza danych, tym trudniej to zrobi\u0107.<\/p>\n<p><\/p>\n<p>Ale zazwyczaj odk\u0142adam mysz dalej i po prostu pisz\u0119 kod. Powiedzmy, musz\u0119 dowiedzie\u0107 si\u0119, jakie tabele (i z jakimi w\u0142a\u015bciwo\u015bciami) znajduj\u0105 si\u0119 w schemacie \"HR\". W wi\u0119kszo\u015bci system\u00f3w zarz\u0105dzania baz\u0105 danych potrzebny wynik mo\u017cna uzyska\u0107 nast\u0119puj\u0105cym prostym zapytaniem z information_schema:<\/p>\n<p><\/p>\n<pre><code class=\"sql\">select table_name\n     , ...\n  from information_schema.tables\n where schema = 'HR'<\/code><\/pre>\n<p><\/p>\n<p>Od bazy do bazy zawarto\u015b\u0107 takich tabel referencyjnych r\u00f3\u017cni si\u0119 w zale\u017cno\u015bci od mo\u017cliwo\u015bci ka\u017cdej bazy danych. Na przyk\u0142ad, dla MySQL z tej samej tablicy mo\u017cna uzyska\u0107 specyficzne dla tej bazy dane tabeli:<\/p>\n<p><\/p>\n<pre><code class=\"sql\">select table_name\n     , storage_engine -- U\u017cywany \"silnik\" (\"MyISAM\", \"InnoDB\" itd.)\n     , row_format     -- Format wiersza (\"Fixed\", \"Dynamic\" itd.)\n     , ...\n  from information_schema.tables\n where schema = 'HR'<\/code><\/pre>\n<p><\/p>\n<p>Oracle nie obs\u0142uguje information_schema, ale ma <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Oracle_metadata\">metadane Oracle<\/a><\/noindex>, i nie ma wi\u0119kszych problem\u00f3w:<\/p>\n<p><\/p>\n<pre><code class=\"sql\">select table_name\n     , pct_free       -- Minimalna ilo\u015b\u0107 wolnego miejsca w bloku danych (%)\n     , pct_used       -- Minimalna ilo\u015b\u0107 zaj\u0119tego miejsca w bloku danych (%)\n     , last_analyzed  -- Data ostatniego zbierania statystyk\n     , ...\n  from all_tables\n where owner = 'HR'<\/code><\/pre>\n<p><\/p>\n<p>Nie inaczej jest z ClickHouse:<\/p>\n<p><\/p>\n<pre><code class=\"sql\">select name\n     , engine -- U\u017cywany \"silnik\" (\"MergeTree\", \"Dictionary\" itd.)\n     , ...\n  from system.tables\n where database = 'HR'<\/code><\/pre>\n<p><\/p>\n<p>Co\u015b podobnego mo\u017cna zrobi\u0107 tak\u017ce w Cassandrze (gdzie zamiast tabeli s\u0105 family kolumn, a zamiast schemat\u00f3w keyspace&#8217;y):<\/p>\n<p><\/p>\n<pre><code class=\"sql\">select columnfamily_name\n     , compaction_strategy_class  -- Strategia kompresji\n     , gc_grace_seconds           -- Czas \u017cycia danych\n     , ...\n  from system.schema_columnfamilies\n where keyspace_name = 'HR'<\/code><\/pre>\n<p><\/p>\n<p>Dla wi\u0119kszo\u015bci innych baz danych r\u00f3wnie\u017c mo\u017cna wymy\u015bli\u0107 podobne zapytania (nawet w Mongo jest <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.mongodb.com\/manual\/reference\/system-collections\/#%3Cdatabase%3E.system.namespaces\">specjalna kolekcja systemowa<\/a><\/noindex>, kt\u00f3ra zawiera informacje o wszystkich kolekcjach w systemie).<\/p>\n<p><\/p>\n<p>Oczywi\u015bcie w ten spos\u00f3b mo\u017cna uzyska\u0107 informacje nie tylko o tabelach, ale w og\u00f3le o dowolnym obiekcie. Okresowo dobrzy ludzie dziel\u0105 si\u0119 takim kodem dla r\u00f3\u017cnych baz danych, jak na przyk\u0142ad w serii artyku\u0142\u00f3w na Habrze \"Funkcje do dokumentowania baz danych PostgreSQL\" (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/post\/415575\">ajb<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/post\/415897\">ben<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/post\/418597\">gim<\/a><\/noindex>). Oczywi\u015bcie trzymanie ca\u0142ej tej masy zapyta\u0144 w g\u0142owie i ci\u0105g\u0142e ich wpisywanie to \"nieprzyjemna\" uciecha, wi\u0119c w mojej ulubionej IDE\/edytorze mam z g\u00f3ry przygotowany zestaw snippet\u00f3w do cz\u0119sto u\u017cywanych zapyta\u0144 i pozostaje tylko wpisa\u0107 nazwy obiekt\u00f3w w szablon.<\/p>\n<p><\/p>\n<p>W rezultacie taki spos\u00f3b nawigacji i wyszukiwania obiekt\u00f3w jest znacznie bardziej elastyczny, oszcz\u0119dza du\u017co czasu, pozwala uzyska\u0107 dok\u0142adnie te informacje i w takim formacie, w jakim s\u0105 teraz potrzebne (jak opisano na przyk\u0142ad w po\u015bcie <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/company\/JetBrains\/blog\/342094\">\"Eksport danych z bazy danych w dowolnym formacie: co potrafi\u0105 IDE na platformie IntelliJ\"<\/a><\/noindex>).<\/p>\n<p><\/p>\n<h5 id=\"operacii-s-obektami\">Operacje z obiektami<\/h5>\n<p><\/p>\n<p>Po tym, jak znale\u017ali\u015bmy i przeanalizowali\u015bmy potrzebne obiekty, nasta\u0142 czas na zrobienie z nimi czego\u015b u\u017cytecznego. Oczywi\u015bcie, r\u00f3wnie\u017c nie odrywaj\u0105c palc\u00f3w od klawiatury.<\/p>\n<p><\/p>\n<p>Nie jest tajemnic\u0105, \u017ce proste usuni\u0119cie tabeli b\u0119dzie wygl\u0105da\u0107 w zasadzie identycznie w niemal wszystkich bazach danych:<\/p>\n<p><\/p>\n<pre><code class=\"sql\">drop table hr.persons<\/code><\/pre>\n<p><\/p>\n<p>Stworzenie tabeli jest ju\u017c znacznie ciekawsze. Praktycznie ka\u017cda DBMS (w tym wiele NoSQL) w jakiej\u015b formie obs\u0142uguje 'create table', a g\u0142\u00f3wna cz\u0119\u015b\u0107 nawet niewiele si\u0119 r\u00f3\u017cni (nazwa, lista kolumn, typy danych), ale pozosta\u0142e szczeg\u00f3\u0142y mog\u0105 si\u0119 znacznie r\u00f3\u017cni\u0107 i zale\u017c\u0105 od wewn\u0119trznej struktury i mo\u017cliwo\u015bci konkretnej DBMS. Moim ulubionym przyk\u0142adem s\u0105 dokumenty Oracle, kt\u00f3re zawieraj\u0105 jedynie 'surowe' BNF dla sk\u0142adni 'create table' <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.oracle.com\/en\/database\/oracle\/oracle-database\/19\/sqlrf\/sql-language-reference.pdf\">zajmuj\u0105 31 stron<\/a><\/noindex>. Inne SGBD maj\u0105 bardziej skromne mo\u017cliwo\u015bci, ale ka\u017cda z nich tak\u017ce dysponuje wieloma interesuj\u0105cymi i unikalnymi funkcjami przy tworzeniu tabel (<noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/static\/sql-createtable.html\">postgres<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/create-table.html\">mysql<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.cockroachlabs.com\/docs\/stable\/create-table.html#expanded\">cockroach<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.datastax.com\/en\/cql\/3.3\/cql\/cql_reference\/cqlCreateTable.html\">cassandra<\/a><\/noindex>). Z pewno\u015bci\u0105 \u017caden graficzny 'wizard' z kolejnej IDE (szczeg\u00f3lnie uniwersalnej) nie b\u0119dzie w stanie w pe\u0142ni pokry\u0107 wszystkich tych zdolno\u015bci, a je\u015bli ju\u017c, to b\u0119dzie to spektakl nie dla s\u0142abych nerw\u00f3w. Z drugiej strony, poprawnie i na czas napisany operator <strong><em>create table<\/em><\/strong> pozwoli bez trudu skorzysta\u0107 ze wszystkich z nich, zapewniaj\u0105c, \u017ce przechowywanie i dost\u0119p do twoich danych b\u0119d\u0105 niezawodne, optymalne i maksymalnie komfortowe.<\/p>\n<p><\/p>\n<p>Wiele DBMS ma r\u00f3wnie\u017c swoje specyficzne typy obiekt\u00f3w, kt\u00f3re nie wyst\u0119puj\u0105 w innych DBMS. Co wi\u0119cej, mo\u017cemy wykonywa\u0107 operacje nie tylko na obiektach DB, ale tak\u017ce na samej DBMS, na przyk\u0142ad 'zabi\u0107' proces, zwolni\u0107 jak\u0105\u015b przestrze\u0144 pami\u0119ci, w\u0142\u0105czy\u0107 \u015bledzenie, przej\u015b\u0107 w tryb 'read only' i wiele innych.<\/p>\n<p><\/p>\n<h5 id=\"a-teper-nemnogo-porisuem\">A teraz troch\u0119 pokreatywujemy<\/h5>\n<p><\/p>\n<p>Jednym z najcz\u0119stszych zada\u0144 jest zbudowanie diagramu z obiektami DB, aby na \u0142adnym obrazku zobaczy\u0107 obiekty i powi\u0105zania mi\u0119dzy nimi. Potrafi to praktycznie ka\u017cda graficzna IDE, osobne narz\u0119dzia 'command line', specjalistyczne narz\u0119dzia graficzne i modele. Kt\u00f3re co\u015b narysuj\u0105 'jak potrafi\u0105', a nieco wp\u0142yn\u0105\u0107 na ten proces mo\u017cna tylko przy pomocy kilku parametr\u00f3w w pliku konfiguracyjnym lub zaznacze\u0144 w interfejsie.<\/p>\n<p><\/p>\n<p>Ale ten problem mo\u017cna rozwi\u0105za\u0107 znacznie pro\u015bciej, elastyczniej i elegancko, oczywi\u015bcie za pomoc\u0105 kodu. Do rysowania diagram\u00f3w o dowolnym poziomie skomplikowania mamy kilka specjalistycznych j\u0119zyk\u00f3w znacznik\u00f3w (DOT, GraphML itd.), a do nich ca\u0142\u0105 gam\u0119 aplikacji (GraphViz, PlantUML, Mermaid), kt\u00f3re potrafi\u0105 czyta\u0107 takie instrukcje i wizualizowa\u0107 w najr\u00f3\u017cniejszych formatach. A informacje o obiektach i ich powi\u0105zaniach ju\u017c wiemy, jak zdoby\u0107.<\/p>\n<p><\/p>\n<p>Podajmy ma\u0142y przyk\u0142ad, jak to mog\u0142oby wygl\u0105da\u0107, z u\u017cyciem PlantUML i <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/316428\/\">demonstracyjnej bazy danych dla PostgreSQL<\/a><\/noindex> (po lewej SQL zapytanie, kt\u00f3re wygeneruje potrzebn\u0105 instrukcj\u0119 dla PlantUML, a po prawej rezultat):<\/p>\n<p>\n<img decoding=\"async\" alt=\"Do\u015bwiadczenie \u00abDatabase as Code\u00bb\" src=\"\/wp-content\/uploads\/2020\/06\/c5cb2a138df1527abe47cbf8d695cb36.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"sql\">select '@startuml'||chr(10)||'hide methods'||chr(10)||'hide stereotypes' union all\nselect distinct ccu.table_name || ' --|&gt; ' ||\n       tc.table_name as val\n  from table_constraints as tc\n  join key_column_usage as kcu\n    on tc.constraint_name = kcu.constraint_name\n  join constraint_column_usage as ccu\n    on ccu.constraint_name = tc.constraint_name\n where tc.constraint_type = 'FOREIGN KEY'\n   and tc.table_name ~ '.*' union all\nselect '@enduml'<\/code><\/pre>\n<p><\/p>\n<p>A je\u015bli troch\u0119 si\u0119 postarasz, to na podstawie <noindex><a rel=\"nofollow\" href=\"https:\/\/gist.github.com\/QuantumGhost\/0955a45383a0b6c0bc24f9654b3cb561\">szablonu ER dla PlantUML<\/a><\/noindex> mo\u017cna uzyska\u0107 co\u015b mocno przypominaj\u0105cego prawdziwy diagram ER:<\/p>\n<p><\/p>\n<p>                        <b class=\"spoiler_title\">Zapytanie SQL jest o odrobin\u0119 bardziej skomplikowane<\/b><\/p>\n<pre><code class=\"sql\">-- Nag\u0142&oacute;wek\nselect &#039;@startuml\n        !define Table(name,desc) class name as &quot;desc&quot; &lt;&lt; (T,#FFAAAA) &gt;&amp;gt;\n        !define primary_key(x) &lt;b&gt;x&lt;\/b&gt;\n        !define unique(x) &lt;color:green&gt;x&lt;\/color&gt;\n        !define not_null(x) &lt;u&gt;x&lt;\/u&gt;\n        hide methods\n        hide stereotypes&#039;\n union all\n-- Tabele\nselect format(&#039;Table(%s, &quot;%s n informacje o %s&quot;) {&#039;||chr(10), table_name, table_name, table_name) ||\n       (select string_agg(column_name || &#039; &#039; || upper(udt_name), chr(10))\n          from information_schema.columns\n         where table_schema = &#039;public&#039;\n           and table_name = t.table_name) || chr(10) || &#039;}&#039;\n  from information_schema.tables t\n where table_schema = &#039;public&#039;\n union all\n-- Relacje mi\u0119dzy tabelami\nselect distinct ccu.table_name || &#039; &quot;1&quot; --&amp;gt; &quot;0..N&quot; &#039; || tc.table_name || format(&#039; : &quot;A %s mo\u017ce mie\u0107 wiele %s&quot;&#039;, ccu.table_name, tc.table_name)\n  from information_schema.table_constraints as tc\n  join information_schema.key_column_usage as kcu on tc.constraint_name = kcu.constraint_name\n  join information_schema.constraint_column_usage as ccu on ccu.constraint_name = tc.constraint_name\n where tc.constraint_type = &#039;FOREIGN KEY&#039;\n   and ccu.constraint_schema = &#039;public&#039;\n   and tc.table_name ~ &#039;.*&#039;\n union all\n-- Stopka\nselect &#039;@enduml&#039;<\/code><\/pre>\n<p>\n<img decoding=\"async\" alt=\"Do\u015bwiadczenie \u00abDatabase as Code\u00bb\" src=\"\/wp-content\/uploads\/2020\/06\/bd45420a4fd1bdfcf20cbe74a0638c7b.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p>Je\u015bli uwa\u017cnie si\u0119 przyjrzysz, to pod mask\u0105 wiele narz\u0119dzi wizualizacyjnych u\u017cywa podobnych zapyta\u0144. Jednak s\u0105 one zazwyczaj g\u0142\u0119boko <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/pgmodeler\/pgmodeler\/blob\/9c615c0b0871df3cd649983ce61e802b0ff0137b\/schemas\/catalog\/table.sch\">'osadzone' w kodzie samej aplikacji i trudne do zrozumienia<\/a><\/noindex>, nie m\u00f3wi\u0105c ju\u017c o jakiejkolwiek ich modyfikacji.<\/p>\n<p><\/p>\n<h5 id=\"metriki-i-monitoring\">Metryki i monitoring<\/h5>\n<p><\/p>\n<p>Przejd\u017amy do tradycyjnie trudnego tematu \u2014 monitorowania wydajno\u015bci baz danych. Przypomn\u0119 sobie kr\u00f3tk\u0105 prawdziw\u0105 histori\u0119, kt\u00f3r\u0105 opowiedzia\u0142 mi \"jeden z moich przyjaci\u00f3\u0142\". Na jednym z projekt\u00f3w \u017cy\u0142 sobie pot\u0119\u017cny DBA, i niewielu deweloper\u00f3w mia\u0142o z nim osobisty kontakt, a w zasadzie kiedykolwiek widzia\u0142o go na oczy (mimo \u017ce rzekomo pracowa\u0142 gdzie\u015b w pobliskim budynku). W godzinie \"X\", gdy produkcyjny system du\u017cego detalisty zaczyna\u0142 po raz kolejny \"\u017ale si\u0119 czu\u0107\", milcz\u0105co wysy\u0142a\u0142 zrzuty ekranu z wykresami z oraklowego Enterprise Managera, na kt\u00f3rych starannie zaznacza\u0142 krytyczne miejsce czerwonym markerem dla \"jasno\u015bci\" (co, m\u00f3wi\u0105c delikatnie, ma\u0142o pomaga\u0142o). W oparciu o t\u0119 \"fotokartk\u0119\" trzeba by\u0142o leczy\u0107. Przy tym nikt nie mia\u0142 dost\u0119pu do cennego (w obu znaczeniach tego s\u0142owa) Enterprise Managera, poniewa\u017c system jest skomplikowany i drogi, nagle \"deweloperzy co\u015b popsuj\u0105 i wszystko si\u0119 zepsuje\". Dlatego deweloperzy \"empirycznie\" znajdowali miejsce i przyczyn\u0119 spowolnie\u0144, a nast\u0119pnie wydawali \u0142atki. Je\u015bli gro\u017any list od DBA nie nadesz\u0142o ponownie w nadchodz\u0105cym czasie, wszyscy z ulg\u0105 oddychali i wracali do swoich bie\u017c\u0105cych zada\u0144 (a\u017c do nowego Listu).<\/p>\n<p><\/p>\n<p>Ale proces monitorowania mo\u017ce wygl\u0105da\u0107 znacznie bardziej weso\u0142o i przyja\u017anie, a co najwa\u017cniejsze \u2014 by\u0107 dost\u0119pny i przejrzysty dla wszystkich. Przynajmniej jego podstawowa cz\u0119\u015b\u0107, jako dodatek do g\u0142\u00f3wnych system\u00f3w monitorowania (kt\u00f3re s\u0105 niew\u0105tpliwie pomocne i w wielu przypadkach niezb\u0119dne). Ka\u017cda SGBD swobodnie i ca\u0142kowicie bezp\u0142atnie gotowa jest podzieli\u0107 si\u0119 informacjami o swoim aktualnym stanie i wydajno\u015bci. W tej samej \"krwawej\" Oracle DB praktycznie wszystkie informacje o wydajno\u015bci mo\u017cna uzyska\u0107 z widok\u00f3w systemowych, poczynaj\u0105c od proces\u00f3w i sesji, a ko\u0144cz\u0105c na stanie pami\u0119ci podr\u0119cznej (na przyk\u0142ad, <noindex><a rel=\"nofollow\" href=\"https:\/\/oracle-base.com\/dba\/scripts\">Skrypty DBA<\/a><\/noindex>, sekcja \"Monitoring\"). W Postgresql r\u00f3wnie\u017c istnieje ca\u0142a gama widok\u00f3w systemowych do <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/static\/monitoring-stats.html\">monitorowania dzia\u0142ania baz danych<\/a><\/noindex>, w szczeg\u00f3lno\u015bci takie niezb\u0119dne w codziennej pracy ka\u017cdego DBA, jak <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/static\/monitoring-stats.html#PG-STAT-ACTIVITY-VIEW\">pg_stat_activity<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/static\/monitoring-stats.html#PG-STAT-DATABASE-VIEW\">pg_stat_database<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/docs\/current\/static\/monitoring-stats.html#PG-STAT-BGWRITER-VIEW\">pg_stat_bgwriter<\/a><\/noindex>. W MySQL z tego powodu jest nawet oddzielny schemat <noindex><a rel=\"nofollow\" href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/performance-schema-table-descriptions.html\">performance_schema<\/a><\/noindex>. A w MongoDB wbudowany <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.mongodb.com\/manual\/tutorial\/manage-the-database-profiler\/\">profiler<\/a><\/noindex> agreguje dane o wydajno\u015bci w kolekcji systemowej <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.mongodb.com\/manual\/tutorial\/manage-the-database-profiler\/#view-profiler-data\">system.profile<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p>W ten spos\u00f3b, uzbrojony w dowolny program do zbierania metryk (Telegraf, Metricbeat, Collectd), kt\u00f3ry potrafi wykonywa\u0107 niestandardowe zapytania SQL, oraz system przechowywania tych metryk (InfluxDB, Elasticsearch, Timescaledb) i wizualizator (Grafana, Kibana), mo\u017cna stworzy\u0107 stosunkowo prosty i elastyczny system monitorowania, kt\u00f3ry b\u0119dzie \u015bci\u015ble zintegrowany z innymi metrykami systemowymi (uzyska\u0107 je mo\u017cna na przyk\u0142ad z serwera aplikacji, systemu operacyjnego itp.). Jak to jest zrealizowane w pgwatch2, gdzie u\u017cywana jest kombinacja InfluxDB + Grafana oraz zestaw zapyta\u0144 do widok\u00f3w systemowych, do kt\u00f3rych mo\u017cna r\u00f3wnie\u017c <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/cybertec-postgresql\/pgwatch2#adding-metrics\">doda\u0107 niestandardowe zapytania<\/a><\/noindex>.<\/p>\n<p><\/p>\n<h2 id=\"itogo\">Podsumowuj\u0105c<\/h2>\n<p><\/p>\n<p>I to tylko przybli\u017cony wykaz tego, co mo\u017cna zrobi\u0107 z nasz\u0105 baz\u0105 danych za pomoc\u0105 standardowego kodu SQL. Jestem pewien, \u017ce mo\u017cna znale\u017a\u0107 jeszcze wiele zastosowa\u0144, piszcie w komentarzach. A o tym, jak (a co najwa\u017cniejsze, dlaczego) mo\u017cna to wszystko zautomatyzowa\u0107 i w\u0142\u0105czy\u0107 do swojego pipeline'u CI\/CD, porozmawiamy nast\u0119pnym razem.<\/p>\n<p>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/426833\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>SQL, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0449\u0435? \u041a\u0430\u0436\u0434\u044b\u0439 \u0438\u0437 \u043d\u0430\u0441 \u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u2014 \u043d\u0430\u0431\u0438\u0440\u0430\u0435\u043c select, \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043a\u043e\u043b\u043e\u043d\u043a\u0438, \u0437\u0430\u0442\u0435\u043c from, \u0438\u043c\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u044b, \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0443\u0441\u043b\u043e\u0432\u0438\u0439 \u0432 where \u0438 \u0432\u0441\u0435 \u2014 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0443 \u043d\u0430\u0441 \u0432 \u043a\u0430\u0440\u043c\u0430\u043d\u0435, \u043f\u0440\u0438\u0447\u0435\u043c (\u043f\u043e\u0447\u0442\u0438) \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u043e\u0442 \u0442\u043e\u0433\u043e \u043a\u0430\u043a\u0430\u044f \u0421\u0423\u0411\u0414 \u0432 \u044d\u0442\u043e \u0432\u0440\u0435\u043c\u044f \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c (\u0430 \u043c\u043e\u0436\u0435\u0442 \u0438 \u043d\u0435 \u0421\u0423\u0411\u0414 \u0432\u043e\u0432\u0441\u0435). \u0412 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":83650,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-83649","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"SQL, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0449\u0435?\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/database-as-sode-experience\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u00abDatabase as \u0421ode\u00bb Experience | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"SQL, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0449\u0435?\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/database-as-sode-experience\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-06-02T05:42:21+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-06-02T05:42:21+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47\u00abDatabase as Code\u00bb Experience | ProHoster","description":"SQL, czy mo\u017ce by\u0107 co\u015b prostszego?","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/database-as-sode-experience","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u00abDatabase as \u0421ode\u00bb Experience | ProHoster","og:description":"SQL, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0449\u0435?","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/database-as-sode-experience","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-06-02T05:42:21+00:00","article:modified_time":"2020-06-02T05:42:21+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"83649","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 15:13:28","updated":"2022-10-02 10:23:46","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/83649","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=83649"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/83649\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/83650"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=83649"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=83649"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=83649"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}