MySQL jest wolnodostępnym, otwartoźródłowym systemem zarządzania relacyjną bazą danych RDBMS. Korzysta z języka SQL (Structured Query Language), w którym opisujemy strukturę danych, pobieramy rekordy, modyfikujemy je oraz nadajemy uprawnienia użytkownikom.
MySQL często występuje w stosie LAMP: Linux, Apache, MySQL, PHP W praktyce spotkasz też MariaDB, czyli rozwijany niezależnie fork MySQL-a. Większość podstawowych poleceń działa w obu systemach, ale przy nowych funkcjach zawsze warto sprawdzić dokumentację konkretnej wersji.
Relacyjna baza danych przechowuje informacje w tabelach. Tabela składa się z kolumn, czyli pól o określonym
typie danych, oraz rekordów, czyli pojedynczych wierszy. Relacje między tabelami opisuje się najczęściej
przez klucze: PRIMARY KEY identyfikuje rekord, a FOREIGN KEY wskazuje rekord w innej tabeli.
Przykładowy podział:
users - użytkownicy aplikacji.orders - zamówienia.orders.user_id - klucz obcy wskazujący users.id.# logowanie lokalne
mysql -u root -p
# logowanie do konkretnej bazy na zdalnym hoście
mysql -h host.example.com -P 3306 -u user -p nazwa_bazy
SHOW DATABASES;
USE nazwa_bazy;
SHOW TABLES;
DESCRIBE users;
SHOW CREATE TABLE users;
SELECT DATABASE();
SELECT VERSION();
exit;
CREATE DATABASE sklep
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
USE sklep;
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
name VARCHAR(100) NOT NULL,
role ENUM('user', 'admin') NOT NULL DEFAULT 'user',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uniq_users_email (email)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_0900_ai_ci;
W nowych projektach najczęściej używaj InnoDB, jawnego PRIMARY KEY, utf8mb4 oraz zapisu dat w formacie
YYYY-MM-DD HH:MM:SS.
Dobrze dobrany typ danych porządkuje model, zmniejsza rozmiar indeksów i ogranicza błędne dane już na poziomie bazy.
| Typ | Zastosowanie |
|---|---|
TINYINT |
małe liczby, często flagi 0/1 |
SMALLINT, MEDIUMINT |
zakresy pośrednie |
INT |
najczęstszy typ dla identyfikatorów i liczników |
BIGINT |
bardzo duże liczniki i identyfikatory |
DECIMAL(10,2) |
kwoty, ceny, wartości wymagające dokładności |
FLOAT, DOUBLE |
liczby przybliżone, np. pomiary, statystyki |
BIT |
wartości bitowe |
UNSIGNED usuwa wartości ujemne i zwiększa dodatni zakres typu całkowitego. INT(11) nie oznacza większego
zakresu niż INT; szerokość wyświetlania dla typów całkowitych jest przestarzała. Do pieniędzy używaj
DECIMAL, nie FLOAT.
| Typ | Zastosowanie |
|---|---|
CHAR(n) |
tekst stałej długości, np. kod kraju |
VARCHAR(n) |
typowy krótki tekst zmiennej długości |
TEXT, MEDIUMTEXT, LONGTEXT |
dłuższe treści |
BINARY, VARBINARY |
dane binarne o ograniczonej długości |
BLOB, MEDIUMBLOB, LONGBLOB |
większe dane binarne |
ENUM(...) |
jedna wartość z zamkniętej listy |
SET(...) |
wiele wartości z zamkniętej listy |
JSON |
dokument JSON z walidacją składni przez MySQL |
VARCHAR jest dobry dla nazw, maili i tytułów. TEXT wybieraj dla artykułów, opisów i większych bloków treści.
Pliki zwykle lepiej trzymać w systemie plików lub storage, a w bazie zapisywać ścieżkę i metadane.
| Typ | Zastosowanie |
|---|---|
DATE |
sama data |
TIME |
sama godzina lub czas trwania |
DATETIME |
data i czas bez automatycznej konwersji strefy czasowej |
TIMESTAMP |
znacznik czasu konwertowany względem strefy sesji |
YEAR |
rok |
Do większości dat aplikacyjnych wygodny jest DATETIME. TIMESTAMP jest dobry dla technicznych znaczników czasu,
ale ma węższy zakres i zachowuje się z uwzględnieniem strefy czasowej połączenia.
CHARACTER SET mówi, jakie znaki można zapisać. COLLATE mówi, jak MySQL porównuje i sortuje tekst.
To ma wpływ na WHERE, ORDER BY, LIKE, UNIQUE, indeksy i wyszukiwanie.
SHOW CHARACTER SET LIKE 'utf8%';
SHOW COLLATION LIKE 'utf8mb4%';
SELECT @@character_set_server, @@collation_server;
SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME = 'sklep';
Najważniejsze zasady:
utf8mb4 obsługuje pełniejszy Unicode, w tym emoji i znaki spoza BMP.utf8mb3 zapisuje maksymalnie 3-bajtowe znaki Unicode, więc może mieć problem z emoji.utf8 w MySQL nie oznacza obecnie „pełnego UTF-8”; to przestarzały alias utf8mb3.utf8 nie jest synonimem domyślnego ustawienia bazy. Domyślne kodowanie baza dziedziczy z serwera, jeśli nie podasz go jawnie.utf8mb4, a domyślne sortowanie dla niego to utf8mb4_0900_ai_ci.Suffix w nazwie mówi o sposobie porównywania:
_ci - case-insensitive, czyli A i a są traktowane jako równe._cs - case-sensitive._ai - accent-insensitive, czyli np. część akcentów może być ignorowana._as - accent-sensitive._bin - porównanie binarne, zwykle dokładne i case-sensitive.Przykłady:
CREATE TABLE tags (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) COLLATE utf8mb4_0900_ai_ci NOT NULL,
slug VARCHAR(100) COLLATE utf8mb4_bin NOT NULL,
UNIQUE KEY uniq_tags_slug (slug)
) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Kolumny z _bin, np. utf8mb4_bin, są dobre dla tokenów, hashy, slugów i identyfikatorów, gdzie Abc
ma być czymś innym niż abc. Starsze nazwy w stylu utf8_bin dotyczą w praktyce aliasu utf8mb3_bin;
do nowych tabel używaj raczej utf8mb4_bin.
utf8mb4_general_ci bywa szybsze, bo jest prostsze i nie jest oparte na pełnym Unicode Collation Algorithm.
utf8mb4_unicode_ci oraz utf8mb4_0900_ai_ci dają zwykle lepszą zgodność językową, ale mogą być cięższe.
Nie wybieraj collation wyłącznie pod szybkość. Najpierw ustal oczekiwane porównywanie tekstu, a dopiero potem
mierz wydajność na realnych danych.
ALTER DATABASE sklep
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
ALTER TABLE users
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
ALTER TABLE users
MODIFY email VARCHAR(255)
CHARACTER SET utf8mb4
COLLATE utf8mb4_bin
NOT NULL;
ALTER DATABASE zmienia ustawienie domyślne dla nowych tabel, ale nie konwertuje automatycznie istniejących
kolumn. Do istniejących tabel używaj ALTER TABLE ... CONVERT TO CHARACTER SET ... albo jawnej zmiany kolumn.
Przed migracją wykonaj backup i sprawdź długości indeksów, bo utf8mb4 może wymagać więcej bajtów na znak.
INSERT INTO users (email, name)
VALUES ('jan@example.com', 'Jan');
INSERT INTO users (email, name)
VALUES
('anna@example.com', 'Anna'),
('adam@example.com', 'Adam');
SELECT id, email, name
FROM users;
UPDATE users
SET name = 'Jan Kowalski'
WHERE id = 1;
DELETE FROM users
WHERE id = 1;
Przy UPDATE i DELETE prawie zawsze podawaj WHERE. Bez niego zmienisz lub usuniesz wszystkie rekordy.
Przed dużą zmianą warto najpierw uruchomić SELECT z tym samym warunkiem.
SELECT id, email
FROM users
WHERE email LIKE '%@example.com'
AND created_at >= '2026-01-01'
ORDER BY created_at DESC
LIMIT 10 OFFSET 20;
Przydatne operatory:
=, <> lub !=, >, <, >=, <= - porównania.AND, OR, NOT - łączenie warunków.IN (...) - dopasowanie do listy wartości.BETWEEN a AND b - zakres domknięty.LIKE - wzorzec tekstowy; % oznacza dowolny ciąg znaków, _ jeden znak.IS NULL, IS NOT NULL - porównanie z brakiem wartości.Logiczna kolejność czytania zapytania jest inna niż kolejność pisania: FROM, JOIN, WHERE,
GROUP BY, HAVING, SELECT, ORDER BY, LIMIT.
NULL oznacza brak wartości, a nie pusty tekst, zero ani false.
SELECT *
FROM users
WHERE updated_at IS NULL;
CREATE TABLE newsletter_subscribers (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
confirmed_at DATETIME NULL DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Jeśli pole jest obowiązkowe, dodaj NOT NULL. Jeśli brak wartości ma znaczenie biznesowe, zostaw NULL
i obsługuj go jawnie przez IS NULL lub IS NOT NULL.
SELECT user_id,
COUNT(*) AS orders_count,
SUM(total) AS total_sum,
AVG(total) AS avg_total,
MIN(created_at) AS first_order,
MAX(created_at) AS last_order
FROM orders
WHERE status = 'paid'
GROUP BY user_id
HAVING SUM(total) > 1000
ORDER BY total_sum DESC;
WHERE filtruje pojedyncze rekordy przed grupowaniem. HAVING filtruje gotowe grupy, dlatego może
korzystać z funkcji agregujących. W nowych wersjach MySQL zwykle działa tryb ONLY_FULL_GROUP_BY, więc
kolumny w SELECT powinny być zagregowane albo zależne od kolumn z GROUP BY.
SELECT users.email, orders.id, orders.total
FROM users
INNER JOIN orders ON orders.user_id = users.id;
SELECT users.email, orders.id, orders.total
FROM users
LEFT JOIN orders ON orders.user_id = users.id;
SELECT users.email, COUNT(orders.id) AS orders_count
FROM users
LEFT JOIN orders ON orders.user_id = users.id
GROUP BY users.id, users.email;
INNER JOIN zwraca tylko pasujące rekordy z obu tabel. LEFT JOIN zwraca wszystkie rekordy z lewej
tabeli i dopasowania z prawej, a przy braku dopasowania wartości NULL. MySQL nie ma natywnego
FULL OUTER JOIN; najczęściej zastępuje się go połączeniem LEFT JOIN i RIGHT JOIN przez UNION.
SELECT a.id, b.id
FROM a
LEFT JOIN b ON b.a_id = a.id
UNION
SELECT a.id, b.id
FROM a
RIGHT JOIN b ON b.a_id = a.id;
CREATE TABLE orders (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
total DECIMAL(10,2) NOT NULL,
status VARCHAR(30) NOT NULL DEFAULT 'new',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_orders_user
FOREIGN KEY (user_id) REFERENCES users(id)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT chk_orders_total CHECK (total >= 0)
);
Najważniejsze ograniczenia:
PRIMARY KEY - unikalny identyfikator rekordu, nie może być NULL.UNIQUE - wymusza unikalność wartości lub kombinacji wartości.FOREIGN KEY - pilnuje spójności relacji między tabelami.NOT NULL - wymusza podanie wartości.DEFAULT - ustawia wartość domyślną.CHECK - wymusza prosty warunek logiczny.CREATE INDEX idx_orders_status_created ON orders (status, created_at);
CREATE UNIQUE INDEX uniq_users_email ON users (email);
DROP INDEX idx_orders_status_created ON orders;
EXPLAIN
SELECT *
FROM orders
WHERE status = 'paid'
ORDER BY created_at DESC;
Indeksy przyspieszają filtrowanie, sortowanie i łączenie, ale spowalniają zapis i zajmują miejsce.
Najczęściej indeksuje się klucze obce, kolumny używane w WHERE, JOIN, ORDER BY oraz kolumny unikalne.
Indeks wielokolumnowy działa najskuteczniej od lewej strony, np. indeks (status, created_at) pomaga przy
warunku po status i sortowaniu po created_at.
ALTER TABLE zmienia strukturę tabeli: dodaje i usuwa kolumny, zmienia typy, nazwy, indeksy, constraints,
engine, komentarze oraz charsety. Na dużych tabelach może blokować zapis lub przebudować tabelę, więc większe
migracje planuj poza szczytem ruchu.
-- dodanie kolumny
ALTER TABLE users
ADD COLUMN last_login_at DATETIME NULL AFTER updated_at;
-- zmiana typu i definicji kolumny bez zmiany nazwy
ALTER TABLE users
MODIFY COLUMN name VARCHAR(150) NOT NULL;
-- zmiana nazwy kolumny
ALTER TABLE users
RENAME COLUMN name TO full_name;
-- starsza składnia: zmiana nazwy i definicji
ALTER TABLE users
CHANGE COLUMN full_name name VARCHAR(150) NOT NULL;
-- usunięcie kolumny
ALTER TABLE users
DROP COLUMN last_login_at;
-- zmiana nazwy tabeli
ALTER TABLE users
RENAME TO app_users;
-- kilka zmian naraz
ALTER TABLE app_users
ADD COLUMN deleted_at DATETIME NULL,
ADD INDEX idx_app_users_deleted_at (deleted_at);
Przed DROP COLUMN, zmianą typu lub konwersją charsetu wykonaj backup. Przy dużych tabelach sprawdź też,
czy Twoja wersja MySQL wykona zmianę jako INSTANT, INPLACE czy COPY.
Konto w MySQL ma postać 'user'@'host'. Host jest częścią tożsamości konta, więc 'app'@'localhost'
i 'app'@'%' to różne konta.
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'trudne_haslo';
GRANT SELECT, INSERT, UPDATE, DELETE
ON sklep.*
TO 'app_user'@'localhost';
SHOW GRANTS FOR 'app_user'@'localhost';
REVOKE DELETE
ON sklep.*
FROM 'app_user'@'localhost';
DROP USER 'app_user'@'localhost';
Zakresy uprawnień:
*.*sklep.*sklep.ordersSELECT (id, email) ON sklep.usersTypowe przywileje aplikacyjne:
SELECT - odczyt danych.INSERT - dodawanie danych.UPDATE - modyfikacja danych.DELETE - usuwanie danych.CREATE, ALTER, DROP, INDEX - migracje struktury.REFERENCES - tworzenie kluczy obcych.CREATE VIEW, SHOW VIEW - widoki.EXECUTE - procedury i funkcje składowane.W aplikacji nie używaj konta root. Nie dawaj ALL PRIVILEGES, jeśli konto potrzebuje tylko odczytu i zapisu
w jednej bazie. Unikaj hosta '%', jeśli możesz ograniczyć połączenia do localhost albo konkretnego adresu.
Nie nadawaj dostępu do tabel systemowej bazy mysql, jeśli nie jest to konto administracyjne.
CREATE ROLE 'app_rw';
GRANT SELECT, INSERT, UPDATE, DELETE
ON sklep.*
TO 'app_rw';
GRANT 'app_rw' TO 'app_user'@'localhost';
SET DEFAULT ROLE 'app_rw' TO 'app_user'@'localhost';
Role ułatwiają zarządzanie uprawnieniami, gdy wiele kont ma ten sam zestaw przywilejów.
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- albo, gdy coś poszło źle:
ROLLBACK;
Transakcje pozwalają wykonać kilka operacji jako jedną całość. Są szczególnie ważne przy płatnościach,
stanach magazynowych i zmianach w wielu tabelach naraz. W InnoDB transakcje są podstawowym narzędziem
utrzymania spójności danych.
CREATE VIEW active_users AS
SELECT id, email, name
FROM users
WHERE deleted_at IS NULL;
DROP VIEW active_users;
Widok zapisuje zapytanie pod nazwą i może uprościć raporty. Procedury i funkcje składowane mogą przenieść część logiki do bazy, ale w typowych aplikacjach PHP warto używać ich ostrożnie, bo utrudniają wersjonowanie logiki.
# eksport bazy
mysqldump -u user -p sklep > sklep.sql
# eksport z procedurami, triggerami i eventami
mysqldump -u user -p --routines --triggers --events sklep > sklep_full.sql
# import bazy
mysql -u user -p sklep < sklep.sql
Backup jest wartościowy dopiero wtedy, gdy wiesz, że da się go odtworzyć. Przed większą migracją struktury tabel wykonaj kopię i sprawdź import na środowisku testowym.
SELECT NOW(); -- bieżąca data i czas
SELECT CONCAT(name, ' <', email, '>') FROM users;
SELECT LOWER(email), UPPER(name) FROM users;
SELECT ROUND(total, 2) FROM orders;
SELECT COALESCE(updated_at, created_at) FROM users;
SELECT
email,
CASE
WHEN created_at >= '2026-01-01' THEN 'nowy'
ELSE 'starszy'
END AS typ_konta
FROM users;
SELECT * w kodzie aplikacji, jeśli potrzebujesz tylko kilku kolumn.snake_case.utf8mb4, InnoDB, PRIMARY KEY i sensowne indeksy.EXPLAIN.password_hash() i password_verify().Materiał przygotował dla Was:
Andrzej EZNAWCA Mazur
Zapraszam na moje strony:
LekcjePHP.pl
Eznawca.pl