Bu sayfa makine çevirisidir. İngilizce orijinalini okuyun. English

IBSurgeon kütüphanesi

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.

sql
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:

  1. 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)

  1. Dashboard verilerini zamanlamayla 1 süreçle (yani robot) hesaplayın ve basit bir sorguyla alınmaya hazır basit bir tabloya kaydedin

  2. Verileri toplamak ve kullanıma hazır şekilde saklamak için tetikleyiciler kullanın

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

delphi
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:

  1. Kayıt sayısını FIRST/SKIP/ROWS ile sınırlayın

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

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

delphi
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:

  1. Toplu işlemler kullanarak birden fazla satırı tek seferde yükleyin

  2. Grid için ana sorguyu, detay sorgusunu onun bir parçası olarak çalıştıracak şekilde geliştirin

  3. Grid’in görünen kısmı için detayları yüklemek üzere açık bir düğme ekleyin

  4. Kaydırma sırasında anlık sorguları önlemek için detayları almak üzere sorguyu çalıştırmaya gecikme ekleyin

  5. 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:

  1. Aralığı artırın!

  2. Açık (kullanıcı tarafından tetiklenen) yenilemeler uygulayın

  3. 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:

  1. Firebird 4+‘a geçin, ara çöp toplama vardır

  2. Uzun süre çalışan yazılabilir işlemleri açık tutmayın, uygun çöp toplamayı yapın

  3. 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):

sql
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:

sql
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:

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

delphi
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:

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

sql
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:

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

sql
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:

sql
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!

delphi
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:

delphi
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