MySQL'de Aksana Duyarsız Collation Kullanmak
Selamlar, bu yazımda MySQL tarafında aksana duyarsız (accent-insensitive) collation kullanımına bakacağız. Konu basit gibi görünüyor: kullanıcı 'cafe' yazdığında 'café' de gelsin istiyoruz. Ama altına biraz indiğinizde unique key tuzaklarına, indeks kullanımına ve hatta Türkçe karakterlerle ilgili nahoş sürprizlere dönüşebiliyor. Hadi sırayla gidelim.
Aksana duyarsızlık nedir?
Aksana duyarsız (accent-insensitive, kısaca AI) collation'lar, bir karakterin aksanlı ve aksansız hallerini karşılaştırma sırasında eşit kabul eder. Yani e, é, è, ê hepsi aynı sayılır. Buna bir de case-insensitive (CI, harf büyüklüğüne duyarsız) eklenince ortaya utf8mb4_0900_ai_ci gibi MySQL 8.0 varsayılanı çıkıyor. Kullanıcının arama kutusuna 'cafe' yazıp 'Café'yi de bulmasını istiyorsanız tam aradığınız davranış bu.
Sonek mantığı şöyle: _ai aksan duyarsız, _as aksan duyarlı, _ci harf büyüklüğüne duyarsız, _cs duyarlı. Bunları kafanıza kazırsanız geri kalan her şey sadece kombinasyon.
Hangi collation'lar var?
Mevcut seçenekleri görmek için:
SHOW COLLATION WHERE Charset = 'utf8mb4' AND Collation LIKE '%_ai%';
utf8mb4 için pratikte sık karşılaştığınız üç tane var:
utf8mb4_0900_ai_ci: Hem case hem aksan duyarsız. MySQL 8.0+ varsayılanı.utf8mb4_0900_as_ci: Aksan duyarlı, case duyarsız.utf8mb4_0900_as_cs: Her ikisine de duyarlı, yani 'Café' ile 'cafe' farklı.utf8mb4_unicode_ci: Eski ama hala karşınıza çıkar; AI ve CI davranışı verir, ama Unicode 5.2.0 tabanlıdır. 8.0 sonrası için 0900 ailesi tercihim.
Kolon seviyesinde tanımlamak
Tabloyu kurarken collation'ı doğrudan kolon üzerinde belirleyebilirsiniz:
CREATE TABLE places (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL
);
INSERT INTO places (name) VALUES ('Cafe'), ('Café');
SELECT * FROM places WHERE name = 'Cafe';
Bu sorgu iki satırı da döndürür. Kullanıcı arama kutusuna ne yazarsa yazsın, aksanlı eşleşmeyi yakalarsınız. Bence yeni bir tablo açıyorsanız davranışı kolon seviyesinde sabitlemek en temizi; sorgu zamanında oynamak yerine şemayla niyetinizi belli edin.
Unique key tuzağı
AI collation altında unique constraint koyduğunuzda, aksanlı varyantlar da çakışma sayar. Bunu kaçıran bir migration sonrası prod'da Duplicate entry patlamasıyla karşılaşmak çok eğlenceli olmuyor:
CREATE TABLE brands (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
UNIQUE KEY uq_name (name)
);
INSERT INTO brands (name) VALUES ('Naive');
INSERT INTO brands (name) VALUES ('Naive');
İkinci satır 'Duplicate entry' hatası verir. Eğer 'Naive' ile 'Naive'i ayrı kayıtlar olarak tutmak istiyorsanız o zaman ya kolonu utf8mb4_0900_as_cs yapın, ya da unique'i kaldırıp uygulamanızda normalleştirme katmanı kurun.
Sorgu zamanında inline COLLATE
Bazen şemayı değiştiremiyor ama tek bir sorguda aksana duyarsız davranış istiyorsunuz. O zaman:
SELECT id, name
FROM places
WHERE name COLLATE utf8mb4_0900_ai_ci = 'resume';
İşe yarar, Resume, Resume, resume hepsini yakalar. Ama bedeli var: kolonun kendi collation'ı farklıysa, optimizer mevcut indeksi kullanamaz. Tam scan'a düşersiniz. Sık çağrılan bir sorguysa, kolonu istediğiniz collation'la yeniden tanımlayıp indeksi de ona göre kurmak çok daha hızlıdır:
ALTER TABLE places
MODIFY COLUMN name VARCHAR(200)
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
Türkçe ve dotted-i tuzağı
Burası önemli. utf8mb4_0900_ai_ci Unicode'un genel kurallarını izler, Türkçeye özgü harf eşlemelerini bilmez. Yani sizin için 'I' ile 'i', 'I' ile 'i' ayrı çiftlerdir; ama collation 'I' ile 'i'yi büyük-küçük çifti olarak görür. Sonuç: kullanıcı 'Istanbul' (büyük noktasız I) ile 'istanbul' aramasını farklı sonuçlar bekleyebilir, ama collation onu eşit sayar. Türkçeye özgü dilsel kurallar isterseniz utf8mb4_turkish_ci kullanmanız gerekir; ama bu da 0900 ailesi kadar güncel değil.
Şahsi kanaatim: kullanıcı arama kutusunda yazdıklarını uygulama tarafında normalize edin (lowercase'e çekerken mb_strtolower($s, 'UTF-8') ile Türkçe locale'i hesaba katın), sonra MySQL'in collation'ına bel bağlayın. Tek bir katmanın her şeyi çözmesini beklemek yerine sorumluluğu paylaştırmak daha sağlıklı.
Sık karşılaşılan hatalar
- Default collation'a güvenmek: MySQL versiyonunuza göre default değişir. Şemanızda explicit yazın.
- Inline COLLATE'i indekste unutmak: Kolonun collation'ından farklı bir inline COLLATE indeksinizi devre dışı bırakır.
- Unique key altında AI seçmek: Aksanlı varyantları ayrı tutmanız gerekiyorsa AS_CS gerekir.
- Türkçe için generic collation beklemek:
0900_ai_ciTürkçe dotted-i kurallarını uygulamaz; uygulama tarafında normalize edin.
Kapanış
utf8mb4_0900_ai_ci aksanlı/aksansız eşlemeyi şeffafça yapar ve son kullanıcı aramaları için çoğu zaman istediğiniz şey budur. Davranışı kolon seviyesinde sabitleyin, indeks dostu olsun; unique constraint'lerde nelerin çakışacağına dikkat edin. Türkçe konusunda ise collation tek başına yetmez, uygulama katmanında da bir normalizasyon adımı kurmanızı tavsiye ederim. Umarım faydalı olur.
