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.
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:
- 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)
-
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
-
Verileri toplamak ve kullanıma hazır saklamak için tetikleyiciler kullanın
-
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.
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:
-
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ı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.
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:
-
Toplu işlemler kullanarak aynı anda birden fazla satır yükleyin
-
Izgara için ana sorguyu, ayrıntı sorgusunu da içerecek şekilde geliştirin
-
Izgaranın görünür kısmı için ayrıntıları yüklemek üzere açık bir düğme ekleyin
-
Kaydırma sırasında anlık sorguları önlemek için ayrıntıları alacak sorguyu çalıştırmak için gecikme ekleyin
-
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:
-
Aralığı artırın!
-
Açık (kullanıcı tarafından tetiklenen) yenilemeler uygulayın
-
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:
-
Firebird 4+ sürümüne geçin, ara çöp toplama vardır
-
Uzun süreli yazılabilir işlemleri açık tutmayın, uygun çöp toplama 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-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):
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:
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:
-- 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.
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:
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.
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:
-- 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:
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!
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:
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
-
Sorularınızı [email protected] adresine gönderin
-
Firebird Destekçisi olun (ayda 10 EUR’dan başlayan) ve kapalı ileri düzey webinarlara katılın!
-
https://store.firebirdsql.org/p/firebird-associate-donation-eur/