Bazy danych dla admina
Po tej lekcji: kursant instaluje i zabezpiecza MariaDB i PostgreSQL, zakłada użytkownika z dostępem tylko do jednej bazy, rozumie, jak baza sprawdza, kto się loguje, i robi oraz odtwarza kopię przez mysqldump i pg_dump.
Wstęp
W każdej firmie przychodzi taki wieczór. I wtedy są dwa rodzaje adminów. Ci, którzy odpisują „mam, daj mi pięć minut”. I ci, którzy aktualizują CV.
Baz danych nie musisz umieć projektować. Od tego są programiści. Ale postawić ją, zabezpieczyć, dać aplikacji konto i zrobić kopię to czysta robota admina. Dziś przerobimy to na dwóch najpopularniejszych darmowych bazach.
Co to jest serwer bazy danych
Wyobraź sobie archiwum w piwnicy urzędu. Regały z segregatorami, a przy wejściu archiwista. Nikt nie wchodzi między regały sam. Podchodzisz do okienka, mówisz „poproszę wszystkie faktury z marca”, archiwista sprawdza, czy masz uprawnienia, i przynosi.
Serwer bazy danych to właśnie ten archiwista. To zwykły proces, który działa cały czas, trzyma dane w plikach na dysku i wpuszcza do nich tylko przez swoje okienko. Okienko ma numer: MariaDB słucha na porcie 3306, PostgreSQL na 5432. A język, którym mówisz do archiwisty, to SQL.
W środku są bazy, czyli osobne regały, zwykle jeden na aplikację. W bazie są tabele, czyli segregatory. I są użytkownicy bazy. Uwaga: to nie są użytkownicy Linuksa. To osobna lista osób wpuszczanych przez archiwistę.
Dlaczego nie trzymać danych w zwykłym pliku? Bo jak sto osób naraz kupuje kapcie, plik się rozsypie. Archiwista pilnuje kolejki, żeby dwie osoby nie pisały w tym samym miejscu naraz.
Dwie rodziny
MariaDB. W 2009 roku Oracle ogłosił przejęcie firmy Sun, do której należał MySQL. Michael „Monty” Widenius, jeden z autorów MySQL, bał się, że baza przestanie być naprawdę otwarta. Wziął kod i zrobił własną odmianę. MySQL nazwał kiedyś po starszej córce, My. Nową bazę po młodszej, Marii. Gdyby miał trzecie dziecko, mielibyśmy trzecią bazę.
MariaDB mówi tym samym językiem co MySQL. Komendy mysql i mysqldump dalej działają, to drogowskazy do mariadb i mariadb-dump.
PostgreSQL. Starszy brat z uniwersytetu. Projekt POSTGRES zaczął się w 1986 roku na Uniwersytecie Kalifornijskim w Berkeley pod kierunkiem profesora Michaela Stonebrakera, a w 1996 dostał obecną nazwę. Ma opinię bardziej rygorystycznego i bogatszego w funkcje. Programiści mówią na niego „Postgres”.
Dla admina różnica jest prosta: oba robią to samo, tylko inaczej się do nich wchodzi i inaczej robi się kopie. Dlatego poznasz oba.
MariaDB: instalacja
sudo apt install mariadb-server # [Ubuntu], startuje sam
sudo dnf install mariadb-server # [RHEL]
sudo systemctl enable --now mariadb # [RHEL]
sudo ss -ltnp | grep 3306LISTEN 0 80 127.0.0.1:3306 0.0.0.0:* users:(("mariadbd",pid=32377,fd=23))Zobacz adres: 127.0.0.1. Na Ubuntu archiwista domyślnie wpuszcza tylko z tego samego komputera. To dobrze. Baza wystawiona do internetu to jak archiwum z drzwiami od ulicy.
Gdzie konfiguracja? [Ubuntu] /etc/mysql/mariadb.conf.d/50-server.cnf, linijka bind-address = 127.0.0.1. [RHEL] /etc/my.cnf.d/mariadb-server.cnf, sekcja [mysqld]. Dane w obu rodzinach leżą w /var/lib/mysql. Według dokumentacji Red Hata RHEL 10 daje MariaDB 10.11, tak jak Ubuntu 24.04.
Kto jest rootem w bazie
Wejdźmy do środka:
sudo mariadbBez hasła. Weszło. Zanim krzykniesz „dziura!”, zobacz, jak jest ustawione konto:
SHOW CREATE USER root@localhost\GCREATE USER `root`@`localhost` IDENTIFIED VIA mysql_native_password USING 'invalid' OR unix_socketunix_socket znaczy: archiwista pyta Linuksa, kim jest proces, który puka. Jeśli to root Linuksa, wpuszcza jako roota bazy. Hasło jest ustawione na invalid, czyli niemożliwe do wpisania. Tak jest od MariaDB 10.4. Zwykły użytkownik systemu nie wejdzie:
ERROR 1698 (28000): Access denied for user 'eryk'@'localhost'To jak pracownik archiwum, którego wpuszcza się po identyfikatorze firmowym, a nie po haśle. Hasła nie da się ukraść, bo go nie ma.
Zabezpieczenie
MariaDB ma gotowy skrypt, który sprząta po instalacji. Na Ubuntu nazywa się mariadb-secure-installation, a stara nazwa mysql_secure_installation też działa:
sudo mariadb-secure-installationPyta po kolei:
Enter current password for root (enter for none):
Switch to unix_socket authentication [Y/n]
Change the root password? [Y/n]
Remove anonymous users? [Y/n]
Disallow root login remotely? [Y/n]
Remove test database and access to it? [Y/n]
Reload privilege tables now? [Y/n]Na pierwsze pytanie Enter. Na Ubuntu root już jest chroniony przez unix_socket, więc skrypt sam pisze, że możesz odpowiedzieć n przy zmianie hasła. Na resztę odpowiadasz Y: usuń anonimowych gości, zabroń roota z sieci, wywal testową bazę. To jak wymiana zamków po wprowadzeniu się do mieszkania. Trwa minutę, a nikt nie wie, ile kluczy rozdał poprzedni lokator.
Użytkownik i baza
Zasada numer jeden: aplikacja nigdy nie używa roota. Każda dostaje własne konto i własny regał. Jak ktoś włamie się do sklepu z kapciami, dostanie się do kapci, a nie do księgowości.
CREATE DATABASE kapcie CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'kapcie_app'@'localhost' IDENTIFIED BY 'Tajne-Haslo-123';
GRANT ALL PRIVILEGES ON kapcie.* TO 'kapcie_app'@'localhost';
SHOW GRANTS FOR 'kapcie_app'@'localhost';GRANT USAGE ON *.* TO `kapcie_app`@`localhost` IDENTIFIED BY PASSWORD '*6494...'
GRANT ALL PRIVILEGES ON `kapcie`.* TO `kapcie_app`@`localhost`kapcie.* czyta się „każda tabela w bazie kapcie”. USAGE ON *.* brzmi groźnie, ale znaczy „może się zalogować i nic poza tym”. A utf8mb4 to kodowanie, w którym zmieszczą się polskie znaki i emoji. Bez tego ktoś wpisze „Żółw” i zobaczy krzaczki.
Testujemy jako aplikacja:
mariadb -u kapcie_app -p kapcieCREATE TABLE produkty (id INT AUTO_INCREMENT PRIMARY KEY, nazwa VARCHAR(100), cena DECIMAL(8,2));
INSERT INTO produkty (nazwa, cena) VALUES ('Kapcie z pomponem', 49.99), ('Kapcie z GPS', 199.00);
SELECT * FROM produkty;id nazwa cena
1 Kapcie z pomponem 49.99
2 Kapcie z GPS 199.00A teraz próba wejścia do cudzego regału:
ERROR 1044 (42000): Access denied for user 'kapcie_app'@'localhost' to database 'mysql'I o to chodziło. Kapcie widzą tylko kapcie.
PostgreSQL: instalacja i wejście
sudo apt install postgresql # [Ubuntu], startuje sam
sudo dnf install postgresql-server # [RHEL]
sudo postgresql-setup --initdb # [RHEL] tworzy pusty klaster danych
sudo systemctl enable --now postgresql # [RHEL]Na RHEL jest dodatkowy krok, postgresql-setup --initdb. Ubuntu robi go za Ciebie przy instalacji. Na RHEL bez tego usługa po prostu nie wstanie, bo nie ma czego uruchomić.
Ścieżki się rozjeżdżają. [Ubuntu] konfiguracja w /etc/postgresql/16/main/, dane w /var/lib/postgresql/16/main. [RHEL] wszystko razem w /var/lib/pgsql/data/. W obu rodzinach mamy domyślnie wersję 16.
Wchodzimy:
sudo -u postgres psqlW Postgresie głównym administratorem jest konto postgres. I to jest jednocześnie użytkownik Linuksa, i użytkownik bazy. Dlatego najpierw sudo -u postgres, czyli „udawaj użytkownika postgres”, a potem psql.
W środku komendy z ukośnikiem wsteczny to skróty samego psql, nie SQL:
\l lista baz
\du lista użytkowników (ról)
\c baza przejdź do innej bazy
\dt lista tabel
\q wyjścieTworzymy to samo, co w MariaDB:
CREATE ROLE kapcie_app WITH LOGIN PASSWORD 'Tajne-Haslo-123';
CREATE DATABASE kapcie OWNER kapcie_app;W Postgresie użytkownik nazywa się rola. LOGIN znaczy, że ta rola może się logować. A OWNER robi z niej właściciela bazy. Właściciel może w swojej bazie wszystko, w cudzej nic.
pg_hba.conf, czyli bramkarz Postgresa
Próbujemy wejść jako aplikacja:
psql -U kapcie_app kapciepsql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL: Peer authentication failed for user "kapcie_app"Hasło jest dobre, a i tak nie wpuścił. Czemu? Bo Postgres ma bramkarza z listą. Lista leży w pliku pg_hba.conf. Zajrzyjmy na Ubuntu:
local all postgres peer
local all all peer
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256Czytamy jak bramkarz, od góry do pierwszego pasującego wiersza. local to połączenie przez plik gniazda na tym samym komputerze. Metoda peer znaczy: „wpuszczę cię jako kapcie_app tylko wtedy, gdy w Linuksie też jesteś kapcie_app”. A ja byłem innym użytkownikiem. host to połączenie przez sieć, nawet do 127.0.0.1. Tam metoda to scram-sha-256, czyli zwykłe hasło, bezpiecznie przesłane.
Wystarczy więc dodać -h localhost:
psql -h localhost -U kapcie_app kapcieYou are connected to database "kapcie" as user "kapcie_app" on host "localhost" (address "127.0.0.1") at port "5432".To wynik \conninfo. Wszedłem. Ta sama osoba, to samo hasło, tylko inne drzwi.
[RHEL] pg_hba.conf leży w /var/lib/pgsql/data/. Zanim cokolwiek tam zmienisz, przeczytaj, co jest w pliku po --initdb, bo metody mogą być inne niż na Ubuntu. Po zmianie wystarczy sudo systemctl reload postgresql.
Dostęp z innych komputerów to dwie zmiany: listen_addresses w postgresql.conf (domyślnie localhost) i wiersz host z adresem sieci w pg_hba.conf. Plus port w zaporze: sudo ufw allow from 10.0.0.0/24 to any port 5432 [Ubuntu] albo sudo firewall-cmd --permanent --add-service=postgresql [RHEL]. Dla MariaDB to samo: bind-address i usługa mysql w firewalld. Otwieraj tylko dla adresów serwerów aplikacji, nigdy dla całego świata.
Kopie: mysqldump
I wracamy do wiadomości o 23:40. Kopia bazy to nie skopiowanie folderu /var/lib/mysql. Kopiowanie plików działającej bazy to jak fotografowanie archiwum, w którym archiwista właśnie przekłada segregatory. Na zdjęciu połowa segregatorów będzie w powietrzu.
Porządna kopia to zrzut: baza sama wypisuje swoją zawartość jako listę poleceń SQL, które odtworzą ją od zera.
Najpierw hasło. Wpisywanie go w komendzie, -pHaslo, zostawia je w historii Basha i na liście procesów. Lepiej trzymać je w pliku, który czyta tylko właściciel:
nano ~/.my.cnf
chmod 600 ~/.my.cnf[client]
user=kapcie_app
password=Tajne-Haslo-123I zrzut z datą w nazwie, jak w module o skryptach:
mysqldump --single-transaction kapcie > kapcie-$(date +%F).sql
ls -lh kapcie-*.sql
head -3 kapcie-*.sql-rw------- 1 root root 2.1K Nov 3 16:36 kapcie-2026-11-03.sql
-- MariaDB dump 10.19 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: localhost Database: kapcie--single-transaction to ważna flaga. Robi zrzut tak, jakby czas się zatrzymał w jednej chwili, bez blokowania tabel. Sklep działa dalej, a kopia jest spójna. Działa dla tabel w silniku InnoDB, czyli domyślnym.
Odtworzenie. Udajemy katastrofę:
sudo mariadb -e 'DROP TABLE kapcie.produkty;'
sudo mariadb kapcie < kapcie-2026-11-03.sql
sudo mariadb kapcie -e 'SELECT * FROM produkty'id nazwa cena
1 Kapcie z pomponem 49.99
2 Kapcie z GPS 199.00Kapcie wróciły. Programista może iść spać. Ty też.
Wszystkie bazy naraz, od razu spakowane:
sudo mysqldump --all-databases --single-transaction | gzip > wszystko-$(date +%F).sql.gzKopie: pg_dump
W Postgresie hasło trzymasz w pliku ~/.pgpass, też z prawami 600. Format to pięć pól przez dwukropek:
localhost:5432:*:kapcie_app:Tajne-Haslo-123Host, port, baza, użytkownik, hasło. Gwiazdka w miejscu bazy znaczy „dla każdej”. Wpisałem tam najpierw kapcie i potem się dziwiłem, czemu przy innej bazie Postgres znowu pyta o hasło. A jak zapomnisz o chmod 600, Postgres się obrazi:
WARNING: password file "/home/eryk/.pgpass" has group or world access; permissions should be u=rw (0600) or lessI zignoruje plik. Słusznie.
Dwa formaty zrzutu. Zwykły SQL, jak w MariaDB:
pg_dump -h localhost -U kapcie_app kapcie > kapcie-$(date +%F).sqlI własny format Postgresa, -Fc, spakowany i z możliwością odtworzenia wybranych kawałków:
pg_dump -h localhost -U kapcie_app -Fc kapcie -f kapcie-$(date +%F).dump
file kapcie-*.dump
pg_restore -l kapcie-*.dumpkapcie-2026-11-03.dump: PostgreSQL custom database dump - v1.15-0
216; 1259 16387 TABLE public produkty kapcie_app
3394; 0 16387 TABLE DATA public produkty kapcie_app
3249; 2606 16394 CONSTRAINT public produkty produkty_pkey kapcie_apppg_restore -l pokazuje spis treści kopii bez odtwarzania. Jak tar -t z lekcji o archiwach. Odtwarzamy do nowej, pustej bazy, żeby sprawdzić kopię bez ryzyka:
sudo -u postgres createdb -O kapcie_app kapcie_test
pg_restore -h localhost -U kapcie_app -d kapcie_test kapcie-2026-11-03.dump
psql -h localhost -U kapcie_app kapcie_test -c 'SELECT count(*) FROM produkty' count
-------
2I jeszcze jedno. pg_dump nie zapisuje użytkowników, bo użytkownicy w Postgresie są wspólni dla całego serwera, a nie dla jednej bazy. Do tego jest pg_dumpall:
sudo -u postgres pg_dumpall --globals-only > role-$(date +%F).sqlKopia, której nigdy nie odtworzyłeś, to nie kopia. To nadzieja w pliku .sql.
Jak to działa pod spodem
Każda baza to proces, który trzyma pliki z danymi otwarte i na wyłączność. MariaDB to jeden proces mariadbd z wieloma wątkami, po jednym na połączenie. PostgreSQL odpala osobny proces dla każdego połączenia, plus kilka pomocników, które zapisują dane na dysk i sprzątają.
Zanim baza zmieni coś w swoich plikach, najpierw zapisuje w osobnym dzienniku, co zamierza zrobić. W InnoDB to redo log, w Postgresie WAL. Jak prąd zgaśnie w połowie, po starcie baza czyta dziennik i dokańcza albo cofa niedokończone zmiany. Dlatego nie wolno kopiować samych plików działającej bazy: złapiesz dane bez pasującego dziennika.
Zrzut działa inaczej. mysqldump i pg_dump łączą się jak zwykły klient, otwierają transakcję i czytają wszystko tak, jak było w chwili jej otwarcia. Inni dalej piszą, ale zrzut ich zmian nie widzi.
Ściąga flag
| Komenda | Flaga | Co robi |
|---|---|---|
mariadb | -u U -p | loguje jako U, pyta o hasło |
mariadb | -e 'SQL' | wykonuje polecenie i wychodzi |
mariadb | baza < plik.sql | wykonuje polecenia z pliku (odtwarzanie) |
mysqldump | --single-transaction | spójny zrzut bez blokowania tabel (InnoDB) |
mysqldump | --all-databases | wszystkie bazy |
mysqldump | --routines --events --triggers | dodaje procedury, zdarzenia i wyzwalacze |
psql | -h localhost -U U baza | logowanie przez sieć, hasłem |
psql | -c 'SQL' | wykonuje polecenie i wychodzi |
pg_dump | -Fc / -f plik | format własny / zapis do pliku |
pg_restore | -l / -d baza | spis treści / odtworzenie do bazy |
pg_dumpall | --globals-only | tylko role i ustawienia wspólne |
createdb | -O właściciel | tworzy bazę z właścicielem |
⚠ Typowy błąd
Zakładasz użytkownika w MariaDB, wszystko działa lokalnie. Aplikacja na drugim serwerze dostaje:
ERROR 1130 (HY000): Host '10.0.0.15' is not allowed to connect to this MariaDB serverPowody są trzy i sprawdzasz je po kolei. Pierwszy: bind-address = 127.0.0.1, czyli baza w ogóle nie słucha na sieci. Wtedy zobaczysz raczej Can't connect, a w ss -ltn adres 127.0.0.1:3306. Drugi: zapora. Trzeci, ten od komunikatu wyżej: konto 'kapcie_app'@'localhost' pasuje tylko do połączeń z tego samego komputera. Dla aplikacji z 10.0.0.15 potrzebujesz osobnego konta:
CREATE USER 'kapcie_app'@'10.0.0.15' IDENTIFIED BY 'Tajne-Haslo-123';
GRANT ALL PRIVILEGES ON kapcie.* TO 'kapcie_app'@'10.0.0.15';Nie rób w panice 'kapcie_app'@'%'. To jak dać klucz do archiwum każdemu, kto zapuka, byle znał hasło.
W Postgresie odpowiednik to no pg_hba.conf entry for host "10.0.0.15". Ten sam problem, inny bramkarz.
✎ Zadanie
W PostgreSQL utwórz rolę ksiazki_app z hasłem i bazę ksiazki, której ona jest właścicielem. Zaloguj się jako ta rola przez -h localhost, utwórz tabelę z trzema książkami. Zrób zrzut w formacie -Fc, odtwórz go do nowej bazy ksiazki_test i policz wiersze. Bonus: sprawdź, co się stanie, jak spróbujesz zalogować się bez -h localhost, i wyjaśnij to na podstawie pg_hba.conf.
Pokaż rozwiązanie
Rola, baza, właściciel. Najpierw jako postgres:
sudo -u postgres psql -c "CREATE ROLE ksiazki_app WITH LOGIN PASSWORD 'Ksiazki-2026';"
sudo -u postgres psql -c "CREATE DATABASE ksiazki OWNER ksiazki_app;"Potem jako aplikacja, koniecznie z -h localhost:
psql -h localhost -U ksiazki_app ksiazkiCREATE TABLE ksiazki (id SERIAL PRIMARY KEY, tytul TEXT, autor TEXT);
INSERT INTO ksiazki (tytul, autor) VALUES
('Lalka', 'Bolesław Prus'), ('Solaris', 'Stanisław Lem'), ('Ferdydurke', 'Witold Gombrowicz');Zrzut, nowa baza, odtworzenie, liczenie:
pg_dump -h localhost -U ksiazki_app -Fc ksiazki -f ksiazki-$(date +%F).dump
sudo -u postgres createdb -O ksiazki_app ksiazki_test
pg_restore -h localhost -U ksiazki_app -d ksiazki_test ksiazki-$(date +%F).dump
psql -h localhost -U ksiazki_app ksiazki_test -c 'SELECT count(*) FROM ksiazki;' count
-------
3Trzy książki weszły, trzy wyszły. Lepsza frekwencja niż w niejednej bibliotece.
Bonus. Bez -h localhost:
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: FATAL: Peer authentication failed for user "ksiazki_app"Bez -h idziesz przez plik gniazda, czyli wiersz local w pg_hba.conf. Tam stoi peer, więc Postgres pyta Linuksa: „kim jest ten proces?”. Linux odpowiada „eryk”, a nie „ksiazki_app”, i bramkarz zamyka drzwi. Hasła nawet nie zdążył zapytać.
Co dalej
Mamy serwer WWW, mamy bazę. Brakuje kogoś, kto je połączy. W następnej lekcji stawiamy klasyczny stos LAMP od zera: Apache, MariaDB i PHP przez PHP-FPM, i pierwsza aplikacja, która naprawdę czyta kapcie z bazy.