Dlaczego PostgreSQL?
PostgreSQL to nie tylko „darmowy MySQL". To zaawansowany system baz danych z funkcjami, których MySQL nie ma:
- pełne wsparcie dla JSON/JSONB z indeksami GIN
- tablice jako typ kolumny
- Full-text search bez osobnych tabel
- CTE (WITH queries) dla złożonych zapytań
- rozszerzenia:
pgcrypto,uuid-ossp,pgvector
Konfiguracja w Symfony
DATABASE_URL="postgresql://user:secret@localhost:5432/mydb?serverVersion=16&charset=utf8"
Doctrine automatycznie dobierze odpowiedni sterownik i dialekt SQL.
Migracje
# generuje plik migracji na podstawie różnicy encji vs bazy
php bin/console doctrine:migrations:diff
# wykonuje oczekujące migracje
php bin/console doctrine:migrations:migrate
# status migracji
php bin/console doctrine:migrations:status
Zasada: nigdy nie edytuj ręcznie tabel na produkcji. Zawsze przez migracje.
Indeksy — klucz do wydajności
W Doctrine dodajesz indeksy przez atrybuty na encji:
#[ORM\Entity]
#[ORM\Table(name: 'blog_articles')]
#[ORM\Index(fields: ['status'], name: 'idx_status')]
#[ORM\Index(fields: ['publishedAt'], name: 'idx_published_at')]
class BlogArticle
{
// ...
}
Bezpośrednio w SQL:
-- indeks na kolumnie status
CREATE INDEX idx_articles_status ON blog_articles(status);
-- indeks złożony (status + data, malejąco)
CREATE INDEX idx_articles_feed ON blog_articles(status, published_at DESC)
WHERE status = 'published';
EXPLAIN ANALYZE
Najważniejsze narzędzie do diagnozowania wolnych zapytań:
EXPLAIN ANALYZE
SELECT a.*, u.name
FROM blog_articles a
JOIN users u ON u.id = a.author_id
WHERE a.status = 'published'
ORDER BY a.published_at DESC
LIMIT 10;
Szukaj w wyniku:
Seq Scan— pełny skan tabeli (zły znak przy dużych tabelach)Index Scan— używa indeksu (dobrze)cost=— szacunkowy kosztactual time=— realny czas w ms
JSONB — elastyczne dane
Kiedy nie wiesz z góry, jakie pola będzie miał rekord:
ALTER TABLE blog_articles ADD COLUMN meta JSONB DEFAULT '{}';
-- zapisz dowolne dane
UPDATE blog_articles
SET meta = '{"reading_time": 5, "featured": true, "series": "symfony-basics"}'
WHERE id = 1;
-- zapytaj po wartości JSON
SELECT title FROM blog_articles
WHERE meta->>'featured' = 'true';
-- indeks GIN na całym JSON
CREATE INDEX idx_articles_meta ON blog_articles USING GIN(meta);
Porównanie PostgreSQL vs MySQL
| Funkcja | PostgreSQL | MySQL |
|---|---|---|
| JSON z indeksami | ✅ JSONB + GIN | ⚠️ ograniczone |
| Tablice | ✅ | ❌ |
| Full-text search | ✅ wbudowany | ⚠️ słabszy |
| Transakcje DDL | ✅ | ❌ |
| pgvector (AI) | ✅ | ❌ |
| Popularność | rosnąca | malejąca |