15 Firebird Antipattern'i
Alexey Kovyazin tarafından, 14-Oca-2025
Giriş
Bu belge, Firebird veritabanlarıyla çalışırken karşılaşılan 15 yaygın anti-pattern’i ve her biri için çözümleri özetlemektedir.
1. MON$‘a çok sayıda paralel sorgu
Anti-pattern: Çok yaygın bir hata - OnConnect tetikleyicisi, denetim amacıyla kullanıcı bilgilerini seçmek için MON$ATTACHMENTS’e sorgu veya lisanslama amacıyla bağlantı sayısını hesaplamak.
Neden kötü?
-
MON$ tabloları, fbNN_mon_xx sistem dosyalarında saklanan sanal tablolardır; performans istatistikleri vb. içerir
-
Dosya >1Gb ise, onu çok fazla kullanıyorsunuz demektir
-
Yalnızca sistem yöneticilerinin kullanımı için tasarlanmıştır - yani, yalnızca yöneticiler için 1-2 paralel sorgu
-
MON$‘a paralel sorgularla 200+ bağlantı Firebird’ü çok önemli ölçüde yavaşlatır ve 500+ eşzamanlı sorgu Firebird’ü yüksek olasılıkla “askıya alır”
Çözümler:
-
MON$‘u yönetici olmayan görevler için, yani sayım veya denetim için kullanmayın, OnConnect’te kullanmaktan kaçının
-
Denetim amacıyla:
-
CURRENT_USER, CURRENT_TIMESTAMP vb. gibi Bağlam Değişkenlerini kullanın
-
Tetikleyicilerden çok daha güçlü olan yerel Firebird özelliği Audit’i kullanın
-
Lisanslama amacıyla - kullanıcının bağlam değişkenlerini kullanın
2. Yavaş Dashboard Yüklemesi
Anti-pattern: Uygulama başlangıcında son ay veya yıl için tüm siparişleri ve faturaları toplayan kapsamlı dashboard’ları veya skor tablolarını yüklemek veya bazı metrikleri her dakika ya da daha sık güncellemek.
SELECT
SUM(total_sales) as yearly_sales,
COUNT(DISTINCT customers) as customer_count,
AVG(order_value) as avg_order_value
FROM orders
WHERE order_date BETWEEN '2025-01-01' AND '2025-01-01';
Neden kötü?
-
Kullanıcılar gerçek işlerine başlamadan önce şirket geneli istatistikleri görmek için birkaç saniye beklemek zorundadır
-
Firebird açısından - sürekli olarak çok sayıda paralel sorgu çalıştırmak, büyük miktarda veri almak, bunları sıralamak/gruplamak için Firebird birden fazla CPU çekirdeğini yoğun şekilde kullanacak, diskten, önbellekten, sıralama için ayrılmış bellekten okuyacaktır (ve bazen sıralama diske gider)
-
Bu, dakikada birkaç kez rapor oluşturmak gibidir!
Çözümler:
- Dashboard’ları görecek kullanıcı sayısını azaltın:
-
Genellikle Dashboard yalnızca analistler ve yönetim için gereklidir, genel uygulama yükünden hariç tutun
-
Dashboard’ın başlangıçta/bazı formlar için yüklenmesini isteğe bağlı hale getirin, varsayılan olarak devre dışı bırakın
-
Dashboard verilerini başlangıçta değil, açık bir düğme tıklamasıyla yükleyin (yani, bir rapor haline getirin)
-
Dashboard verilerini zamanlamayla 1 süreçle (yani robot) hesaplayın ve basit bir sorguyla alınmaya hazır basit bir tabloya kaydedin
-
Verileri toplamak ve kullanıma hazır şekilde saklamak için tetikleyiciler kullanın
-
Dashboard verilerini (ve tüm ağır raporları da) hesaplamak için replika veritabanı kullanın
3. Gereksiz Kayıtları Yükleme
Anti-pattern: Uygulama veya form açılırken, yüz binlerce kayıt içerip içermediğine bakılmaksızın, tüm verileri filtrelemeden grid’e yüklemek.
procedure TDataForm.LoadAllRecords;
begin
FDQuery1.SQL.Text := 'SELECT * FROM large_table';
FDQuery1.Open;
// Loads entire table into memory
DBGrid1.DataSource.DataSet := FDQuery1;
end;
Neden kötü?
-
Grid yalnızca 50 kayıt gösterse de, kullanıcılar arama işlevselliğini kullanmak yerine binlerce kayıt arasında kaydırmak zorundadır
-
Vakaların %99’unda kullanıcılar çok dar bir veri alt kümesine ihtiyaç duyar: örneğin en son satış kayıtları
-
Firebird açısından:
-
Her açma işlemi binlerce kaydın okunmasını, önbellekte saklanmasını ve ağ üzerinden aktarılmasını gerektirir
-
Veri kümesini açık tutarsanız (Delphi’de), Firebird veri kümesi kapatılana kadar arabellekleri, sıralanmış kayıtları geçici alanda (ORDER BY, GROUP BY vb. varsa) tutar
Çözümler:
-
Kayıt sayısını FIRST/SKIP/ROWS ile sınırlayın
-
Kayıt sayısını bazı kriterlerle sınırlayın, örneğin son 3 gün içinde oluşturulan/değiştirilen kayıtları gösterin
-
Genel olarak, sorguları mümkün olan en kısa sürede kapatın.
4. Kaydırma Sırasında Aşırı Sorgulama
Anti-pattern: Kaydırma olaylarında sorgu çalıştırmak. Örneğin, bir grid veya tabloda veri görüntülerken HER kayıt için ayrı bir sorgu çalıştırmak veya 2 grid’de gecikmesiz klasik master-detail kaydırma örneğini kullanıyorsanız.
procedure TForm1.GridScrolled(Sender: TObject);
begin
// query for each row
FDQuery2.SQL.Text :=
'SELECT additional_info FROM details ' +
'WHERE id = ' + IntToStr(CurrentRowId);
FDQuery2.Open;
end;
Neden kötü?
-
Dinamik grid’de HER kayıt için ayrı bir sorgu çalıştırmak, Firebird’ü binlerce küçük sorguyu işlemeye zorlar ve CPU kaynaklarını gereksiz yere tüketir
-
Firebird açısından:
-
Çok sayıda (saniyede binlerce) küçük sorgu önemli CPU yükü oluşturur, çünkü sorgu istatistiklerde 0ms gösterse bile hazırlanması, çalıştırılması, sonucun aktarılması vb. gerekir
Çözümler:
-
Toplu işlemler kullanarak birden fazla satırı tek seferde yükleyin
-
Grid için ana sorguyu, detay sorgusunu onun bir parçası olarak çalıştıracak şekilde geliştirin
-
Grid’in görünen kısmı için detayları yüklemek üzere açık bir düğme ekleyin
-
Kaydırma sırasında anlık sorguları önlemek için detayları almak üzere sorguyu çalıştırmaya gecikme ekleyin
-
Detayların kaydırma sırasında yüklenmesini varsayılan olarak tüm kullanıcılar için etkinleştirmeyin
5. Gereksiz Otomatik Yenilemeler
Anti-pattern: Her istemci uygulamasında grid verilerini minimum aralıklarla otomatik olarak yenilemek ve bu özelliği varsayılan olarak etkin bırakmak.
Neden kötü?
-
Bu, aynı kayıtları almak için neredeyse aynı sorguları çalıştıran yüzlerce istemci bağlantısıyla sonuçlanır
-
Nerede olur: programlar için otomatik yenilemeler veya kuyruk pozisyonları için seçim veya “en yakın slot” araması vb.
-
Firebird açısından:
-
Dashboard yükleme ve kaydırma olaylarının kombinasyonu: çok sayıda orta boy sorgu sistem üzerinde yük oluşturur
Çözümler:
-
Aralığı artırın!
-
Açık (kullanıcı tarafından tetiklenen) yenilemeler uygulayın
-
Veri kümesinin seçici yenilemelerini gerçek veri değişikliklerine dayalı olarak kullanın (streaming veya tetikleyiciler veya olay+streaming)
6. Sık Kayıt Güncellemeleri
Anti-pattern: Aynı kaydı farklı işlemlerde sık sık güncelleyerek çok sayıda kayıt sürümü oluşturmak.
Neden kötü?
-
Düzinelerce sürüme sahip bir kayıt performansı önemli ölçüde düşürebilir, binlerce sürüme sahip bir kayıt ise engelleyici hale gelebilir
-
Firebird açısından: belirli işlemin doğru sürümünü tanımlamak için kayıt sürümleri zincirinin yeniden oluşturulması gerekir, bu da çok sayıda okuma işlemi gerektirir; sonuç olarak çöp toplama önemli ölçüde yavaşlar.
Çözümler:
-
Firebird 4+‘a geçin, ara çöp toplama vardır
-
Uzun süre çalışan yazılabilir işlemleri açık tutmayın, uygun çöp toplamayı yapın
-
Firebird <4 için UPDATE yerine DELETE+INSERT kullanmayı düşünün
7. Salt okunur seçimler için yazma işlemleri kullanmak
Anti-pattern: Salt okunur seçimler için yazma işlemleri kullanmak aşırı işlemlere yol açar.
Neden kötü?
-
Salt okunur seçimler için yazma işlemleri kullanmak, başlık sayfalarının gereksiz yere çok sayıda yazılmasına yol açar
-
Salt okunur işlemler için yazma işlemleri kullanmak verimsizdir (commit sırasında büyük TIP sunucu üzerinde ek yük oluşturur)
Çözümler:
-
Veriyi değiştirmeyen işlemler için ayrı bir salt okunur işlem kullanın
-
Firebird, tek bir bağlantı çerçevesinde birden fazla işlem açmaya izin veren birkaç veritabanından biridir
-
Global Temporary tabloları salt okunur işlemlerde kullanmak mümkündür
8. LIKE :param kullanımı
Aşağıdaki parametreli sorgu, fieldName alanı için dizini kullanmayacaktır (dizin mevcut olsa bile):
SELECT * FROM Table1 WHERE fieldName LIKE :param1
Neden kötü?
LIKE, herhangi bir sayıda sembolü değiştirebilen joker karakter aramasına (%) izin verdiğinden, Firebird parametre değerinin önceden dizin araması için uygun olup olmayacağını belirleyemez.
Genellikle geliştiriciler bunu parametre değerini sorgu metnine gömerek aşmaya çalışır:
-
fieldName LIKE «Alex%» - dizin kullanmak mümkün
-
fieldName LIKE «%Alex» - standart dizin kullanmak mümkün değil
-
fieldName LIKE «%Alex%» - dizin kullanmak hiç mümkün değil
Bu başka sorunlara yol açar (aşağıda #10’a bakın).
Çözümler:
1. Bilinen dize önekleri için STARTING WITH kullanın
Arama değeriniz hiçbir zaman joker karakter % ile başlamıyorsa, LIKE yerine STARTING WITH tercih edin:
WHERE fieldName STARTING WITH ?param1
2. Çift yönlü dize aramalarını optimize edin
Bilinen önek veya sonek kalıplarına sahip dizeler için ters dizin kullanın:
-- Create reversed index
CREATE INDEX ixreverse1 ON TABLE1 COMPUTED BY (REVERSE(fieldName));
-- Query using both directions
WHERE fieldName STARTING WITH :param1
OR reverse(fieldName) STARTING WITH reverse(:param2)
3. Aşamalı arama stratejisi uygulayın
Başta/sonda/ortada görünen (ancak aynı anda değil) dizeler için:
-
Önce STARTING WITH ile hızlı dizinli aramayı deneyin
-
Sonuç bulunamazsa, daha yavaş LIKE aramasına geri dönün
4. Kelime Tabanlı Arama Optimizasyonu
Tam kelimeleri (boşluk, virgül vb. ile ayrılmış) ararken:
-
Ayrı bir kelime-ID eşleme tablosu oluşturun
-
Orijinal metin yerine eşleme tablosu üzerinden arama yapın
5. Kapsamlı tam metin arama yetenekleri için:
-
IBSurgeon Full Text Search UDR kullanmayı düşünün
-
Bu açık kaynak çözüm, gelişmiş metin arama işlevselliği sağlar
9. Salt okunur işlemler için işlemleri kapatmamak
Neden kötü?
- İşlemleri uzun süreler açık tutmak, Firebird’ü olası snapshot işlemleri için çok sayıda geri sürüm tutmaya zorlayabilir
Çözümler:
-
Mümkün olan yerlerde salt okunur işlemler kullanın ve yazılabilir işlemleri mümkün olan en kısa sürede kapatın
-
Kayıt sürümleri zincirlerinin etkisini azaltmak için modern Firebird sürümlerini (4+) kullanın
-
Uygun sweep uygulayın
10. Sorgu Parametrelendirme Sorunları
Anti-pattern: Hazırlanmış sorgulardan ve parametrelendirmeden kaçınmak, bunun yerine parametre değerlerini doğrudan sorgu metnine gömmek.
FDQuery1.SQL.Text :=
'SELECT * FROM users WHERE name = ''' +
EditUsername.Text + '''';
FDQuery1.Open;
Neden kötü?
-
Bu uygulama, tekrarlanan sorgular için performansı düşürür
-
Parametre değerleri gömülü her sorgu yeni olarak hazırlanmalıdır
-
Büyük tablolar için hazırlık uzun ve zaman alıcı olabilir
-
Sorun analizini zorlaştırır
-
Sorguları metne göre gruplamak zordur
-
SQL enjeksiyon güvenlik açıkları oluşturur
Çözümler:
FDQuery1.SQL.Text :=
'SELECT * FROM users WHERE name = :username';
FDQuery1.ParamByName('username').AsString :=
EditUsername.Text;
FDQuery1.Open;
11. Yanlış Bütünlük Kontrolü: Primary Key yerine tetikleyiciler/CHECK’ler
Anti-pattern: Veritabanı bütünlük kontrolleri için Primary Key yerine tetikleyiciler veya CHECK kullanmak.
Neden kötü?
-
Bu, Primary Key doğrulamasının, kullanıcının işlem yalıtım düzeyinden bağımsız olarak kaydın geçerli sürümünü okumak için özel modu kullandığını göz ardı eder.
-
Kullanıcı işlemlerinde tetikleyicilerle PK kontrolleri yapmak, çoğaltma olasılığını artırır ve mantığı gereksiz yere karmaşıklaştırır
Çözümler:
-
Primary key’ler kullanın
-
Gereksiz bütünlük kontrollerinden kaçının
-
Veritabanı mantığını basit tutun
12. MAX() ile ID üretimi
Anti-pattern: Yeni tanımlayıcılar için MAX(id)+1 kullanmak güvenilmez ve verimsizdir.
INSERT INTO users (id, name)
VALUES ((SELECT MAX(id) + 1 FROM users),
'John Doe');
Neden kötü?
-
Yeni tanımlayıcılar için sequence’lar (generator’lar) yerine MAX(id)+1 kullanmak
-
MAX(id)+1, yaygın işlem parametreleriyle benzersizliği garanti etmez - iki paralel işlem aynı MAX() değerini alabilir
-
Max()+1 ve CHECK(benzersizse seç) kombinasyonu da çalışmaz!
Çözümler:
-- Use generator/sequence!
CREATE GENERATOR gen_user_id;
-- Use generator for ID generation
INSERT INTO users (id, name)
VALUES (
GEN_ID(gen_user_id, 1),
'John Doe' );
13. Verimsiz GUID Kullanımı
Neden kötüdür?
-
gen_uuid() yerine sistem tarafından oluşturulan GUID’lerin kullanılması dizin performansını etkileyebilir
-
Sistem tarafından oluşturulan GUID oldukça rastgeledir
Çözümler:
-
gen_uuid() fonksiyonunu kullanın
-
Bunun yerine BIGINT kullanmayı düşünün
-
Sürüm 6’da UUID v7 olacak
14. Verimsiz Hesaplanan Alanlar
Anti-pattern: Diğer tablolara yönelik SELECT’lerle birlikte hesaplanan alanların kullanılması, basit SELECT işlemlerinin performansını önemli ölçüde düşürür.
CREATE TABLE orders (
id INTEGER,
total_amount COMPUTED BY (
(SELECT SUM(item_price) FROM order_items
WHERE order_items.order_id = orders.id)));
Neden kötüdür?
-
Hesaplanan alanlar anlık olarak hesaplanır ve karmaşık mantık uygulamak için tasarlanmamıştır; optimizasyon çabalarını önemli ölçüde karmaşıklaştırabilirler
-
Tablolar arasındaki ilişkileri güçlendirir
-
Hesaplanan alanları yalnızca tablonun alanlarıyla yapılan birleştirme gibi hafif hesaplamalar için kullanmak mantıklıdır
Çözümler:
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
cached_total_amount DECIMAL(10,2));
CREATE TRIGGER update_order_total
BEFORE INSERT OR UPDATE ON orders
AS
BEGIN
NEW.cached_total_amount = (
SELECT SUM(item_price)
FROM order_items
WHERE order_items.order_id = NEW.id
);
END;
15. Günlükleme Olmadan Hata Bastırma
Anti-pattern: Firebird hatalarını ve uyarılarını günlükleme olmadan bastırmayın!
try
FDQuery1.Open;
except
// Silent failure
end;
Neden kötüdür?
- Hataları gizlemek, doğru teşhis ve hata ayıklamayı engeller. Sorunları hızlı bir şekilde anlamak ve çözmek için doğru hata günlüklemesi çok önemlidir.
Çözümler:
try
FDQuery1.Open;
except
on E: Exception do
begin
// Comprehensive logging
Logger.Error('Database connection failed: ' + E.Message);
ShowMessage('Unable to connect to database. Please contact support.');
// Log additional context
Logger.LogStackTrace(E);
end;
end;
İletişim Bilgileri
-
Sorularınızı [email protected] adresine gönderin
-
Firebird Supporter olun (aylık 10 EUR’dan başlayan) ve kapalı ileri düzey web seminerlerine katılın!
-
https://store.firebirdsql.org/p/firebird-associate-donation-eur/