MySQL, SQL, typy danych, indeksy i uprawnienia MySQL, SQL, data types, indexes and privileges

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.

Baza, tabela, rekord, kolumna

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.

Szybki start w konsoli

# 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;

Tworzenie bazy i tabeli

CREATE DATABASE sklep
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_0900_ai_ci
;

USE 
sklep;

CREATE TABLE users (
    
id INT UNSIGNED AUTO_INCREMENT,
    
email VARCHAR(255NOT NULL,
    
name VARCHAR(100NOT 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.

Typy danych

Dobrze dobrany typ danych porządkuje model, zmniejsza rozmiar indeksów i ogranicza błędne dane już na poziomie bazy.

Typy liczbowe

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.

Typy tekstowe i binarne

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.

Typy daty i czasu

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.

Charsety i collations

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_NAMEDEFAULT_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.
  • Samo utf8 nie jest synonimem domyślnego ustawienia bazy. Domyślne kodowanie baza dziedziczy z serwera, jeśli nie podasz go jawnie.
  • W MySQL 8.0/8.4 domyślny zestaw serwera to utf8mb4, a domyślne sortowanie dla niego to utf8mb4_0900_ai_ci.

Jak czytać nazwy collations

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(100COLLATE utf8mb4_0900_ai_ci NOT NULL,
    
slug VARCHAR(100COLLATE 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.

Zmiana charsetu i collation

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.

Podstawowe operacje CRUD

INSERT INTO users (emailname)
VALUES ('jan@example.com''Jan');

INSERT INTO users (emailname)
VALUES
    
('anna@example.com''Anna'),
    (
'adam@example.com''Adam');

SELECT idemailname
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, filtrowanie i sortowanie

SELECT idemail
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 i wartości domyślne

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(255NOT 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.

Agregacje i grupowanie

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.

Łączenie tabel

SELECT users.emailorders.idorders.total
FROM users
INNER JOIN orders ON orders
.user_id users.id;

SELECT users.emailorders.idorders.total
FROM users
LEFT JOIN orders ON orders
.user_id users.id;

SELECT users.emailCOUNT(orders.id) AS orders_count
FROM users
LEFT JOIN orders ON orders
.user_id users.id
GROUP BY users
.idusers.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.idb.id
FROM a
LEFT JOIN b ON b
.a_id a.id
UNION
SELECT a
.idb.id
FROM a
RIGHT JOIN b ON b
.a_id a.id;

Klucze, relacje i ograniczenia

CREATE TABLE orders (
    
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
user_id INT UNSIGNED NOT NULL,
    
total DECIMAL(10,2NOT NULL,
    
status VARCHAR(30NOT NULL DEFAULT 'new',
    
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    
CONSTRAINT fk_orders_user
        FOREIGN KEY 
(user_idREFERENCES 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.

Indeksy

CREATE INDEX idx_orders_status_created ON orders (statuscreated_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.

Modyfikacja tabel

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
(150NOT NULL;

-- 
zmiana nazwy kolumny
ALTER TABLE users
    RENAME COLUMN name TO full_name
;

-- 
starsza składniazmiana nazwy i definicji
ALTER TABLE users
    CHANGE COLUMN full_name name VARCHAR
(150NOT 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.

Użytkownicy, uprawnienia i przywileje

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 SELECTINSERTUPDATEDELETE
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ń:

  • globalny: *.*
  • baza: sklep.*
  • tabela: sklep.orders
  • kolumny: SELECT (id, email) ON sklep.users

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

Role w MySQL 8

CREATE ROLE 'app_rw';

GRANT SELECTINSERTUPDATEDELETE
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.

Transakcje

START TRANSACTION;

UPDATE accounts SET balance balance 100 WHERE id 1;
UPDATE accounts SET balance balance 100 WHERE id 2;

COMMIT;
-- 
albogdy 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.

Widoki, procedury i funkcje

CREATE VIEW active_users AS
SELECT idemailname
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.

Kopie zapasowe i import

# eksport bazy
mysqldump -u user -p sklep sklep.sql

# eksport z procedurami, triggerami i eventami
mysqldump -u user ---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.

Funkcje przydatne na co dzień

SELECT NOW();                         -- bieżąca data i czas
SELECT CONCAT
(name' <'email'>'FROM users;
SELECT LOWER(email), UPPER(nameFROM users;
SELECT ROUND(total2FROM orders;
SELECT COALESCE(updated_atcreated_atFROM users;

SELECT
    email
,
    CASE
        
WHEN created_at >= '2026-01-01' THEN 'nowy'
        
ELSE 'starszy'
    
END AS typ_konta
FROM users
;

Dobre praktyki

  • Nie pobieraj SELECT * w kodzie aplikacji, jeśli potrzebujesz tylko kilku kolumn.
  • Używaj zapytań parametryzowanych w PHP aby chronić się przed SQL Injection.
  • Nazywaj tabele i kolumny konsekwentnie, np. snake_case.
  • Dla nowych tabel ustawiaj utf8mb4, InnoDB, PRIMARY KEY i sensowne indeksy.
  • Sprawdzaj wolne zapytania przez EXPLAIN.
  • Nie zapisuj haseł użytkowników jawnie; w PHP używaj password_hash() i password_verify().
  • Migracje struktury wykonuj najpierw na kopii, szczególnie przy zmianie typów, indeksów i charsetów.
  • Włączaj ścisły tryb SQL na środowisku developerskim, aby szybciej łapać błędne dane.

Oficjalna dokumentacja


Materiał przygotował dla Was:
Andrzej EZNAWCA Mazur
Zapraszam na moje strony:
LekcjePHP.pl
Eznawca.pl