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

IBSurgeon kütüphanesi

15 Firebird Anti-Desenleri

by Alexey Kovyazin, 14-Oca-2025

Giriş

Bu belge, Firebird veritabanlarıyla çalışırken karşılaşılan 15 yaygın anti-deseni ve her biri için çözümleri özetlemektedir.

1. MON$ üzerinde birden fazla paralel sorgu

Anti-desen: Çok popüler bir hata - OnConnect tetikleyicisi, denetim amaçları için kullanıcı bilgilerini seçmek veya lisanslama amaçları için bağlantı sayısını hesaplamak amacıyla MON$ATTACHMENTS sorgusu.

Neden kötü?

  • MON$ tabloları, fbNN_mon_xx sistem dosyalarında saklanan, performans istatistikleri vb. içeren sanal tablolardır

  • Dosyanın 1Gb’den büyük olması, onu çok fazla kullandığınız anlamına gelir

  • Bunlar yalnızca sistem yöneticileri için tasarlanmıştır - yani, yalnızca yöneticiler için 1-2 paralel sorgu

  • MON$‘a paralel sorgularla 200+ bağlantı Firebird’ü önemli ölçüde yavaşlatır ve 500+ eşzamanlı sorgu, Firebird’ü yüksek olasılıkla “askıya alır”

Çözümler:

  • MON$‘ı sayma veya denetim gibi yönetici olmayan görevler için kullanmayın, OnConnect’te kullanmaktan kaçının

  • Denetim amaçları için:

  • CURRENT_USER, CURRENT_TIMESTAMP gibi Bağlam Değişkenlerini kullanın

  • Denetim - Firebird’ün yerel özelliği, tetikleyicilerden çok daha güçlüdür

  • Lisanslama amaçları için - kullanıcının bağlam değişkenlerini kullanın

2. Yavaş Gösterge Panosu Yüklenmesi

Anti-desen: Uygulama başlangıcında son ay veya yılın tüm siparişlerini ve faturalarını toplayan kapsamlı gösterge panoları veya skor tabloları yüklemek veya bazı metrikleri her dakika veya 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 genelindeki istatistikleri görmek için birkaç saniye beklemek zorundadır

  • Firebird açısından - büyük miktarda veriyi almak, sıralamak/gruplamak için sürekli olarak birçok paralel sorgu çalıştırmak için Firebird, sıralama için ayrılmış diskten, önbellekten, bellekten okuma yaparak birden fazla CPU çekirdeğini yoğun şekilde kullanacaktır (ve bazen sıralama diske gider)

  • Bu, dakikada birkaç kez rapor oluşturmak gibidir!

Çözümler:

  1. Gösterge panosunu görecek kullanıcı sayısını azaltın:
  • Genellikle Gösterge Panosu yalnızca analistler ve yönetim için gereklidir, genel uygulama yükünden hariç tutun

  • Gösterge panosunun başlangıçta/bir formda yüklenmesini isteğe bağlı hale getirin, varsayılan olarak devre dışı bırakın

  • Gösterge panosu verilerini başlangıçta değil, açık bir düğme tıklamasıyla yükleyin (yani, bunu bir rapor gibi yapın)

  1. Gösterge panosu verilerini zamanlamayla (yani robot) 1 işlemle hesaplayın ve basit bir sorguyla alınmaya hazır basit bir tabloda saklayın

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

  3. Gösterge panosu verilerini (ve tüm ağır raporları da) hesaplamak için yedek veritabanı kullanın

3. Gereksiz Kayıtların Yüklenmesi

Anti-desen: Bir uygulamayı veya formu açarken, yüz binlerce kayıt içerip içermediğine bakılmaksızın tüm verileri filtrelemeden ızgaraya yüklemek.

delphi
procedure TDataForm.LoadAllRecords;
begin
 FDQuery1.SQL.Text := 'SELECT * FROM large_table';
 FDQuery1.Open;
 // Tüm tabloyu belleğe yükler
 DBGrid1.DataSource.DataSet := FDQuery1;
end;

Neden kötü?

  • Izgara yalnızca 50 kayıt göstermesine rağmen, kullanıcılar arama işlevini kullanmak yerine binlerce kayıt arasında gezinmek 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çılış, binlerce kaydın ağ üzerinden okunmasını, önbellekte saklanmasını ve aktarılmasını gerektirir

  • Veri kümesini açık tutarsanız (Delphi’de), Firebird, veri kümesi kapanana kadar arabellekleri, geçici alanda sıralanmış kayıtları (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ırmada Aşırı Sorgulama

Anti-desen: Kaydırma olaylarında sorgular çalıştırmak. Örneğin, bir ızgarada veya tabloda veri görüntülerken, HER KAYIT için ayrı bir sorgu çalıştırmak veya gecikmesiz 2 ızgarada klasik ana-ayrıntı kaydırma örneğini kullanıyorsanız.

delphi
procedure TForm1.GridScrolled(Sender: TObject);
begin
 // her satır için sorgu
 FDQuery2.SQL.Text :=
 'SELECT additional_info FROM details ' +
 'WHERE id = ' + IntToStr(CurrentRowId);
 FDQuery2.Open;
end;

Neden kötü?

  • Dinamik ızgarada HER KAYIT için ayrı bir sorgu çalıştırmak, Firebird’ün binlerce küçük sorguyu işlemesini zorunlu kılar ve gereksiz yere CPU kaynaklarını tüketir

  • Firebird açısından:

  • Saniyede birçok (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 aynı anda birden fazla satır yükleyin

  2. Izgara için ana sorguyu, ayrıntı sorgusunu da içerecek şekilde geliştirin

  3. Izgaranın görünür kısmı için ayrıntıları yüklemek üzere açık bir düğme ekleyin

  4. Kaydırma sırasında anlık sorguları önlemek için ayrıntıları alacak sorguyu çalıştırmak için gecikme ekleyin

  5. Kaydırma sırasında ayrıntı yüklemeyi varsayılan olarak tüm kullanıcılar için etkinleştirmeyin

5. Gereksiz Otomatik Yenilemeler

Anti-desen: Her istemci uygulamasında ızgara verilerini minimum aralıklarla otomatik olarak yenilemek ve bu özelliğin varsayılan olarak etkin olması.

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, kuyruk pozisyonları için seçimler veya “en yakın yuva” araması vb.

  • Firebird açısından:

  • Gösterge panoları ve kaydırma olaylarının kombinasyonu: birçok 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. Gerçek veri değişikliklerine dayalı seçici veri kümesi yenilemeleri kullanın (akış veya tetikleyiciler veya olay+akış)

6. Sık Kayıt Güncellemeleri

Anti-desen: Aynı kaydı farklı işlemlerde sık sık güncellemek, çok sayıda kayıt sürümü oluşturmak.

Neden kötü?

  • Düzinelerce sürümü olan bir kayıt performansı önemli ölçüde düşürebilir, binlerce sürümü olan bir kayıt engelleyici olabilir

  • Firebird açısından: belirli bir işlemin doğru sürümünü belirlemek için kayıt sürümleri zinciri yeniden yapılandırılmalıdır, bu çok sayıda okuma işlemi gerektirir ve sonuç olarak çöp toplama önemli ölçüde yavaşlar.

Çözümler:

  1. Firebird 4+ sürümüne geçin, ara çöp toplama vardır

  2. Uzun süreli yazılabilir işlemleri açık tutmayın, uygun çöp toplama 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-desen: 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 birçok kez 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 sunucuda ek yük oluşturur)

Çözümler:

  • Verileri değiştirmeyen işlemler için ayrı 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 Geçici Tablolar salt okunur işlemlerde kullanılabilir

8. LIKE :param kullanımı

Parametreli aşağıdaki sorgu, fieldName 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 dizin araması için uygun olup olmayacağını önceden belirleyemez.

Genellikle geliştiriciler, parametre değerini sorgu metnine gömmeye çalışarak bunu 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ğıdaki #10’a bakın).

Çözümler:

1. Bilinen dize önekleri için STARTING WITH kullanın

Arama değeriniz asla 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 desenlerine sahip dizeler için ters dizin kullanın:

sql
-- Ters dizin oluştur
CREATE INDEX ixreverse1 ON TABLE1 COMPUTED BY (REVERSE(fieldName));
-- Her iki yönü kullanan sorgu
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 ararken (boşluklar, virgüller vb. ile ayrılmış):

  • 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 kaynaklı çö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üre açık tutmak, Firebird’ün olası anlık görüntü işlemleri için çok sayıda geri sürüm tutmasını zorunlu kılabilir

Çözümler:

  • Mümkün olduğunda 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-desen: Hazır 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

  • Gömülü parametre değerleri olan her sorgu yeni olarak hazırlanmalıdır

  • Hazırlık, büyük tablolar için 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ü: Birincil Anahtar yerine tetikleyiciler/CHECK’ler

Anti-desen: Veritabanı bütünlük kontrolleri için Birincil Anahtarlar yerine tetikleyiciler veya CHECK kullanmak.

Neden kötü?

  • Bu, Birincil Anahtar 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:

  • Birincil anahtarlar kullanın

  • Gereksiz bütünlük kontrollerinden kaçının

  • Veritabanı mantığını basit tutun

12. MAX() ile ID üretimi

Anti-desen: 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 diziler (jeneratörler) 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(select if unique) kombinasyonu da çalışmaz!

Çözümler:

sql
-- Jeneratör/dizi kullanın!
CREATE GENERATOR gen_user_id;
-- ID üretimi için jeneratör kullanın
INSERT INTO users (id, name)
VALUES (
 GEN_ID(gen_user_id, 1),
 'John Doe' );

## 13. Verimsiz GUID Kullanımı

**Neden kötüdür?**

- Sistem tarafından üretilen GUID'lerin gen\_uuid() yerine kullanılması indeks performansını etkileyebilir

- Sistem tarafından üretilen GUID oldukça rastgeledir


**Çözümler:**

- gen\_uuid() fonksiyonunu kullanın

- BIGINT kullanmayı düşünün

- 6. sürümde UUID v7 olacak


## 14. Verimsiz Hesaplanmış Alanlar

**Anti-pattern:** Diğer tablolara SELECT sorguları içeren hesaplanmış alanların kullanılması, basit SELECT işlemlerinin performansını önemli ölçüde düşürür.

```sql hljs
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?

  • Hesaplanmış alanlar anında hesaplanır ve karmaşık mantık uygulamak için tasarlanmamıştır; optimizasyon çabalarını önemli ölçüde zorlaştırabilir

  • Tablolar arasındaki ilişkileri güçlendirir

  • Hesaplanmış alanları yalnızca tablonun alanlarıyla yapılan hafif hesaplamalar için kullanmak mantıklıdır, örneğin birleştirme

Çö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 yapmadan bastırmayın!

delphi
try
 FDQuery1.Open;
except
 // Sessiz başarısızlık
end;

Neden kötüdür?

  • Hataları gizlemek doğru teşhis ve hata ayıklamayı engeller. Doğru hata günlüklemesi, sorunları hızlı bir şekilde anlamak ve çözmek için çok önemlidir.

Çözümler:

delphi
try
 FDQuery1.Open;
except
 on E: Exception do
 begin
 // Kapsamlı günlükleme
 Logger.Error('Veritabanı bağlantısı başarısız: ' + E.Message);
 ShowMessage('Veritabanına bağlanılamıyor. Lütfen destek ile iletişime geçin.');
 // Ek bağlamı günlüğe kaydet
 Logger.LogStackTrace(E);
 end;
end;

İletişim Bilgileri