Databases

PostgreSQL for PHP developers — practical tips

Jan Kowalski 26.09.2026 295 wyświetleń 3 komentarzy
This translation was generated by AI and may contain inaccuracies. Read the original (PL)

Why PostgreSQL?

PostgreSQL is not just a "free MySQL". It is an advanced database system with features MySQL doesn't have:

  • full support for JSON/JSONB with GIN indexes
  • arrays as a column type
  • Full-text search without separate tables
  • CTEs (WITH queries) for complex queries
  • extensions: pgcrypto, uuid-ossp, pgvector

Configuration in Symfony

DATABASE_URL="postgresql://user:secret@localhost:5432/mydb?serverVersion=16&charset=utf8"

Doctrine automatically picks the right driver and SQL dialect.


Migrations

# generates a migration file based on the diff between entities and the database
php bin/console doctrine:migrations:diff

# runs pending migrations
php bin/console doctrine:migrations:migrate

# migration status
php bin/console doctrine:migrations:status

Rule: never edit tables manually in production. Always via migrations.


Indexes — the key to performance

In Doctrine you add indexes via attributes on the entity:

#[ORM\Entity]
#[ORM\Table(name: 'blog_articles')]
#[ORM\Index(fields: ['status'], name: 'idx_status')]
#[ORM\Index(fields: ['publishedAt'], name: 'idx_published_at')]
class BlogArticle
{
    // ...
}

Directly in SQL:

-- index on the status column
CREATE INDEX idx_articles_status ON blog_articles(status);

-- composite index (status + date, descending)
CREATE INDEX idx_articles_feed ON blog_articles(status, published_at DESC)
WHERE status = 'published';

EXPLAIN ANALYZE

The most important tool for diagnosing slow queries:

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;

Look for in the result:

  • Seq Scan — full table scan (a bad sign on large tables)
  • Index Scan — uses an index (good)
  • cost= — estimated cost
  • actual time= — real time in ms

JSONB — flexible data

When you don't know the record's fields up front:

ALTER TABLE blog_articles ADD COLUMN meta JSONB DEFAULT '{}';

-- store arbitrary data
UPDATE blog_articles
SET meta = '{"reading_time": 5, "featured": true, "series": "symfony-basics"}'
WHERE id = 1;

-- query by a JSON value
SELECT title FROM blog_articles
WHERE meta->>'featured' = 'true';

-- GIN index on the entire JSON
CREATE INDEX idx_articles_meta ON blog_articles USING GIN(meta);

PostgreSQL vs MySQL comparison

Feature PostgreSQL MySQL
JSON with indexes ✅ JSONB + GIN ⚠️ limited
Arrays ✅ ❌
Full-text search ✅ built-in ⚠️ weaker
Transactional DDL ✅ ❌
pgvector (AI) ✅ ❌
Popularity growing declining
Tagi: #PHP #Symfony #PostgreSQL
Udostępnij: Facebook X LinkedIn

Komentarze (3)


P
Piotr Wiśniewski 21.10.2026, 14:27

JSONB z indeksami GIN to strzał w dziesiątkę dla elastycznych konfiguracji.

J
Jan Kowalski 21.10.2026, 14:27

Dokładnie! Widziałem aplikacje z 2000ms na zapytaniu, po dodaniu indeksu — 3ms.

A
Anna Nowak 21.10.2026, 14:27

Sekcja EXPLAIN ANALYZE powinna być w każdym kursie SQL. Naprawdę otwiera oczy.

Zaloguj się, aby dodać komentarz.