OLTP sistemde makul normalizasyon iyi başlangıçtır
Sipariş alan bir uygulamada müşteri, sipariş, sipariş satırı ve ödeme ayrı kavramlardır. Bunları tek dev tabloya koymak ilk gün kolay görünür; ama müşteri bilgisi değiştiğinde, sipariş geçmişi okunurken veya ödeme durumu güncellenirken tablo hızla karmaşıklaşır.
Makul normalizasyon, her kavramın kendi tablosunda yaşamasını sağlar. Bu sayede veri güncelleme noktası nettir, foreign key ile ilişki korunur ve uygulama kodu hangi verinin gerçeğine bakacağını bilir.
Basit normalleştirilmiş sipariş modeli
CREATE TABLE customers ( id BIGSERIAL PRIMARY KEY, email TEXT NOT NULL UNIQUE, name TEXT NOT NULL ); CREATE TABLE orders ( id BIGSERIAL PRIMARY KEY, customer_id BIGINT NOT NULL REFERENCES customers(id), status TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE order_items ( id BIGSERIAL PRIMARY KEY, order_id BIGINT NOT NULL REFERENCES orders(id), product_id BIGINT NOT NULL, quantity INT NOT NULL, unit_price_cents INT NOT NULL );
Foreign key tek başına yetmez, indeks de gerekir
Foreign key ilişkiyi korur ama her sorguyu otomatik hızlandırmaz. Siparişleri müşteriyle, sipariş satırlarını siparişle okuyan sorgularınız varsa ilgili foreign key kolonlarında indeks gerekir.
İndeks tasarımı sorgu desenine göre yapılmalıdır. Sadece customer_id indekslemek bazı sorgulara yeter; ama müşterinin son siparişlerini okuyorsanız customer_id, created_at kombinasyonu daha anlamlı olabilir.
Join ve listeleme için indeksler
CREATE INDEX idx_orders_customer_created ON orders (customer_id, created_at DESC); CREATE INDEX idx_order_items_order ON order_items (order_id); EXPLAIN (ANALYZE, BUFFERS) SELECT o.id, o.status, o.created_at, sum(oi.quantity * oi.unit_price_cents) AS total_cents FROM orders o JOIN order_items oi ON oi.order_id = o.id WHERE o.customer_id = 42 GROUP BY o.id ORDER BY o.created_at DESC LIMIT 20;
Aşırı normalizasyon da okunabilirliği bozabilir
Her küçük alanı ayrı tabloya bölmek bazen modeli daha doğru değil, daha kırılgan yapar. Örneğin çok az değişen ve sadece ana kayıtla birlikte kullanılan ayarları ayrı ayrı tablolara taşımak gereksiz join ve kod karmaşıklığı yaratabilir.
Normalizasyonu amaç değil araç olarak düşünün. Veri değişim noktalarını netleştiriyor, tutarsızlığı azaltıyor ve sorguları anlaşılır tutuyorsa işe yarıyordur. Sadece şemayı akademik olarak daha saf gösteriyorsa dikkat etmek gerekir.
- Sık birlikte okunan ve aynı yaşam döngüsüne sahip alanlar aynı tabloda kalabilir.
- Tarihsel kayıt için bazı değerler bilinçli kopyalanabilir.
- Raporlama için ayrı özet tablo veya analitik sistem daha doğru olabilir.
- Denormalize alanın güncelleme sahibi yazılı olmalıdır.
Denormalize edecekseniz doğrulama sorgusu yazın
Örneğin orders tablosuna customer_email_snapshot alanı eklemek tarihsel sipariş görünümü için doğru olabilir. Ama customer_current_email gibi güncel kalması gereken bir alan kopyalanıyorsa tutarsızlık riski doğar.
Denormalizasyon yaptığınız her yerde küçük bir doğrulama sorgusu bulunsun. Bu sorgu düzenli çalışmak zorunda değildir; ama sorun anında “hangi kayıtlar ayrışmış?” sorusuna cevap vermelidir.
Tutarsız kopyaları bulma
SELECT o.id, o.customer_id, o.customer_email_copy, c.email AS current_email FROM orders o JOIN customers c ON c.id = o.customer_id WHERE o.customer_email_copy IS DISTINCT FROM c.email LIMIT 100;