Przejdź do treści
Lekcja 92 z 117

Kurs › Moduł 10 › Lekcja 92

Bazy danych dla admina

⏱ ok. 13 min czytania· moduł 10: Usługi i serwery

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 3306
LISTEN 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 mariadb

Bez hasła. Weszło. Zanim krzykniesz „dziura!”, zobacz, jak jest ustawione konto:

SHOW CREATE USER root@localhost\G
CREATE USER `root`@`localhost` IDENTIFIED VIA mysql_native_password USING 'invalid' OR unix_socket

unix_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-installation

Pyta 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 kapcie
CREATE 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.00

A 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 psql

W 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ście

Tworzymy 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 kapcie
psql: 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-256

Czytamy 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 kapcie
You 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-123

I 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.00

Kapcie 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.gz

Kopie: 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-123

Host, 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 less

I zignoruje plik. Słusznie.

Dwa formaty zrzutu. Zwykły SQL, jak w MariaDB:

pg_dump -h localhost -U kapcie_app kapcie > kapcie-$(date +%F).sql

I 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-*.dump
kapcie-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_app

pg_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
-------
     2

I 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).sql

Kopia, 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

KomendaFlagaCo robi
mariadb-u U -ploguje jako U, pyta o hasło
mariadb-e 'SQL'wykonuje polecenie i wychodzi
mariadbbaza < plik.sqlwykonuje polecenia z pliku (odtwarzanie)
mysqldump--single-transactionspójny zrzut bez blokowania tabel (InnoDB)
mysqldump--all-databaseswszystkie bazy
mysqldump--routines --events --triggersdodaje procedury, zdarzenia i wyzwalacze
psql-h localhost -U U bazalogowanie przez sieć, hasłem
psql-c 'SQL'wykonuje polecenie i wychodzi
pg_dump-Fc / -f plikformat własny / zapis do pliku
pg_restore-l / -d bazaspis treści / odtworzenie do bazy
pg_dumpall--globals-onlytylko role i ustawienia wspólne
createdb-O właścicieltworzy 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 server

Powody 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 ksiazki
CREATE 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
-------
     3

Trzy 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.