MySQL’de Charset ve Collation Kaynaklı Hataları Giderme
Veritabanı yönetiminde en can sıkıcı hata kategorilerinden biri şüphesiz charset ve collation sorunlarıdır. Sabah işe geliyorsunuz, uygulama loglarında Illegal mix of collations hatası görüyorsunuz, Türkçe karakterler soru işaretine dönmüş, JOIN sorguları patlıyor. Bu sorunlar genellikle gece yarısı deployment sırasında ya da migration işlemlerinde ortaya çıkar. Bu yazıda bu sorunları köklü şekilde nasıl çözeceğinizi, neden yaşandığını ve production ortamında nasıl önleyeceğinizi adım adım anlatacağım.
Charset ve Collation Nedir, Neden Önemlidir
Charset (karakter seti), veritabanının hangi karakterleri saklayabileceğini belirler. latin1, utf8, utf8mb4 bunların en yaygın örnekleridir. Collation ise bu karakterlerin nasıl karşılaştırılacağını ve sıralanacağını tanımlar. Örneğin utf8mb4_general_ci ile utf8mb4_unicode_ci aynı charset’i kullanır ama karşılaştırma kuralları farklıdır.
MySQL’de utf8 ile utf8mb4 arasındaki farka özellikle dikkat etmek gerekir. MySQL’in utf8 charset’i aslında gerçek UTF-8 değildir, maksimum 3 byte destekler. Bu yüzden emoji gibi 4 byte gerektiren karakterleri saklayamazsınız. utf8mb4 ise gerçek UTF-8 implementasyonudur ve modern uygulamalarda her zaman tercih edilmelidir.
Sık Karşılaşılan Hata Türleri
Illegal Mix of Collations
Bu hata genellikle farklı collation’lara sahip iki kolonu karşılaştırmaya ya da birleştirmeye çalıştığınızda ortaya çıkar.
ERROR 1267 (HY000): Illegal mix of collations (utf8mb4_unicode_ci,IMPLICIT)
and (utf8mb4_general_ci,IMPLICIT) for operation '='
Bu hatayı gören bir sysadmin’in ilk refleksi genellikle COLLATE anahtar kelimesini sorguya eklemek olur. Bu kısa vadeli bir çözümdür ama asıl sorun veritabanı tasarımında yatar.
Incorrect String Value Hatası
Türkçe karakterler veya emoji içeren veriler yazılmaya çalışıldığında karşılaşılan klasik hatadır.
ERROR 1366 (HY000): Incorrect string value: 'xF0x9Fx98x80' for column 'content'
Bu hata genellikle kolon utf8 (3 byte sınırlı) olarak tanımlandığında 4 byte emoji eklenmeye çalışıldığında görülür.
Data Truncation Uyarıları
Bazen hata vermeden veriyi kesen durumlar olur. strict mode aktif değilse MySQL uyarı verip devam edebilir, bu da sessiz veri kaybına yol açar.
Mevcut Durumu Tespit Etmek
Sorunu çözmeden önce nerede olduğunuzu anlamanız gerekir. Şu sorguları çalıştırarak mevcut konfigürasyonu görebilirsiniz.
-- Sunucu seviyesi charset ve collation ayarlarını görüntüle
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
Bu sorgu şu değişkenleri döndürecektir:
- character_set_client: MySQL’e gelen verilerin charset’i
- character_set_connection: Bağlantıda kullanılan charset
- character_set_database: Aktif veritabanının charset’i
- character_set_results: Sonuçların döndürüleceği charset
- character_set_server: Sunucu seviyesi varsayılan charset
Veritabanı ve tablo seviyesinde kontrol etmek için:
-- Veritabanı collation bilgisi
SELECT schema_name, default_character_set_name, default_collation_name
FROM information_schema.schemata
WHERE schema_name = 'veritabani_adiniz';
-- Tablo ve kolon seviyesinde charset/collation bilgisi
SELECT
table_name,
column_name,
character_set_name,
collation_name,
column_type
FROM information_schema.columns
WHERE table_schema = 'veritabani_adiniz'
AND character_set_name IS NOT NULL
ORDER BY table_name, ordinal_position;
Bu sorguyu çalıştırdığınızda aynı veritabanı içinde bazı tabloların latin1_swedish_ci, bazılarının utf8_general_ci, bazılarının da utf8mb4_unicode_ci collation’ına sahip olduğunu görürseniz elinizde ciddi bir temizlik işi var demektir.
MySQL Konfigürasyonunu Doğru Ayarlamak
Sunucu seviyesinde charset ve collation’ı düzgün konfigüre etmek sorunların büyük çoğunluğunu önler. /etc/mysql/mysql.conf.d/mysqld.cnf veya /etc/my.cnf dosyanıza şu ayarları ekleyin:
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
init-connect = 'SET NAMES utf8mb4'
skip-character-set-client-handshake
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
skip-character-set-client-handshake direktifi, istemcinin charset isteğini görmezden gelip sunucu ayarını kullanmasını sağlar. Bu direktif tartışmalıdır, bazı eski uygulamaları kırabilir ama homojen bir ortam istiyorsanız işe yarar.
Konfigürasyonu uygulamak için:
# MySQL servisini yeniden başlat
sudo systemctl restart mysql
# Ayarların geçerli olduğunu doğrula
mysql -u root -p -e "SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'collation%';"
Mevcut Veritabanlarını Migrate Etmek
Production veritabanında charset değişikliği yaparken dikkatli olunması gerekir. Önce mutlaka yedek alın.
# Önce tam yedek al
mysqldump -u root -p --single-transaction --routines --triggers
--default-character-set=utf8mb4 veritabani_adi > yedek_$(date +%Y%m%d_%H%M%S).sql
# Yedek boyutunu ve ilk birkaç satırı kontrol et
ls -lh yedek_*.sql
head -20 yedek_*.sql
Veritabanı seviyesinde charset değişikliği:
-- Veritabanı seviyesinde değiştir
ALTER DATABASE veritabani_adi
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
Dikkat edin, bu komut mevcut tabloları değiştirmez. Sadece yeni oluşturulacak tablolar için varsayılanı değiştirir. Mevcut tabloları da değiştirmek için aşağıdaki yaklaşımı kullanabilirsiniz.
-- Tek bir tablo için charset ve collation değişikliği
ALTER TABLE kullanicilar
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CONVERT TO kullanmak önemlidir. Sadece CHARACTER SET yazsanız tablo varsayılanı değişir ama mevcut kolonlar etkilenmez. CONVERT TO ise tüm metin kolonlarını dönüştürür.
Büyük bir veritabanında tüm tabloları tek tek değiştirmek için bir script yazabilirsiniz:
#!/bin/bash
# tum_tablolari_migrate_et.sh
DB_NAME="veritabani_adi"
DB_USER="root"
DB_PASS="sifre"
TARGET_CHARSET="utf8mb4"
TARGET_COLLATE="utf8mb4_unicode_ci"
# Önce veritabanı seviyesini güncelle
mysql -u "$DB_USER" -p"$DB_PASS" -e
"ALTER DATABASE `$DB_NAME` CHARACTER SET $TARGET_CHARSET COLLATE $TARGET_COLLATE;"
# Tüm tabloları al ve dönüştür
TABLES=$(mysql -u "$DB_USER" -p"$DB_PASS" -N -e
"SELECT table_name FROM information_schema.tables WHERE table_schema='$DB_NAME' AND table_type='BASE TABLE';")
for TABLE in $TABLES; do
echo "Dönüştürülüyor: $TABLE"
mysql -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -e
"ALTER TABLE `$TABLE` CONVERT TO CHARACTER SET $TARGET_CHARSET COLLATE $TARGET_COLLATE;"
if [ $? -eq 0 ]; then
echo " [OK] $TABLE başarıyla dönüştürüldü"
else
echo " [HATA] $TABLE dönüştürülürken hata oluştu"
fi
done
echo "Migration tamamlandı."
Bu script’i production’da çalıştırmadan önce test ortamında deneyin. Büyük tablolarda ALTER TABLE uzun sürebilir ve tabloyu kilitleyebilir. Bu durumda pt-online-schema-change veya gh-ost gibi araçları değerlendirmenizi öneririm.
Gerçek Dünya Senaryosu: E-ticaret Sitesi Krizi
Bir e-ticaret projesinde yaşanan gerçek bir sorunu aktarayım. Uygulama yıllardır latin1 charset ile çalışıyordu. Yeni bir özellik eklenirken ürün açıklamalarına emoji desteği istendi. Geliştirici sadece ilgili kolonu utf8mb4 yaptı, ama ana tablo hala latin1 kaldı. JOIN sorgularında Illegal mix of collations hataları almaya başladık.
Durumu tespit etmek için çalıştırdığımız sorgu:
-- Uyumsuz collation'a sahip kolonları bul
SELECT
t.table_name,
c.column_name,
c.character_set_name,
c.collation_name
FROM information_schema.tables t
JOIN information_schema.columns c
ON t.table_name = c.table_name
AND t.table_schema = c.table_schema
WHERE t.table_schema = 'eticaret_db'
AND c.character_set_name IS NOT NULL
AND c.collation_name != 'utf8mb4_unicode_ci'
ORDER BY t.table_name;
Bu sorgu bize 47 farklı tablo ve 180’den fazla kolonun farklı charset/collation kombinasyonlarına sahip olduğunu gösterdi. Kısa vadeli çözüm olarak kritik JOIN sorgularına COLLATE ekledik:
-- Geçici çözüm: COLLATE ile sorgu düzelt
SELECT u.ad, s.urun_adi
FROM kullanicilar u
JOIN siparisler s ON u.id = s.kullanici_id
WHERE u.sehir COLLATE utf8mb4_unicode_ci = s.teslimat_sehri COLLATE utf8mb4_unicode_ci;
Uzun vadeli çözüm ise maintenance window’da tüm veritabanını migrate etmekti.
Connection String’de Charset Ayarlamak
Uygulama seviyesinde de doğru charset ayarlanmalıdır. Farklı diller için örnekler:
# PHP için PDO connection string
# /var/www/html/config/database.php
# DSN içinde charset belirt
$dsn = "mysql:host=localhost;dbname=veritabani;charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
];
$pdo = new PDO($dsn, $user, $password, $options);
Python ile SQLAlchemy kullananlar için:
# Python SQLAlchemy connection string
# Charset parametresini URL'e ekle
DATABASE_URL = "mysql+pymysql://user:password@localhost/dbname?charset=utf8mb4"
# Veya engine oluştururken
engine = create_engine(
DATABASE_URL,
connect_args={
"charset": "utf8mb4",
"collation": "utf8mb4_unicode_ci"
}
)
Java/JDBC kullananlar ise connection URL’ine şunu eklemeli:
jdbc:mysql://localhost:3306/dbname?characterEncoding=UTF-8&useUnicode=true&connectionCollation=utf8mb4_unicode_ci
utf8mb4_general_ci mi, utf8mb4_unicode_ci mi?
Bu soruyu çok sık alıyorum. Kısa cevap: utf8mb4_unicode_ci kullanın.
- utf8mb4_general_ci: Daha hızlı ama Unicode standartlarına tam uymaz. Bazı dil karşılaştırmalarında yanlış sonuç verebilir. Türkçe’de “I” ve “ı” karşılaştırmalarında sorun yaratabilir.
- utf8mb4_unicode_ci: Unicode standartlarına uyar, daha yavaş ama tutarlı. Türkçe, Almanca gibi özel karakterli dillerde daha güvenilirdir.
- utf8mb4_0900_ai_ci: MySQL 8.0 ile gelen yeni seçenek. Unicode 9.0 standardını destekler, performansı da iyidir. MySQL 8.0 kullanıyorsanız bu seçeneği değerlendirin.
ai suffix’i accent insensitive, ci suffix’i case insensitive anlamına gelir. Eğer A ile a‘yı farklı değerlendirmek istiyorsanız utf8mb4_unicode_cs (case sensitive) kullanmanız gerekir.
Dump ve Restore Sırasında Charset Sorunları
Yedek alıp geri yüklerken charset sorunları yaşanabilir. Doğru yöntem:
# Doğru charset ile dump al
mysqldump --default-character-set=utf8mb4
--single-transaction
--hex-blob
-u root -p veritabani_adi > dump.sql
# Dump dosyasının başını kontrol et
head -5 dump.sql
# /*!40101 SET NAMES utf8mb4 */ satırını görmelisiniz
# Geri yüklerken de charset belirt
mysql --default-character-set=utf8mb4 -u root -p yeni_veritabani < dump.sql
Eğer eski bir dump dosyanız varsa ve içinde /!40101 SET NAMES latin1 / gibi satırlar görüyorsanız, bu satırları sed ile değiştirebilirsiniz:
# Dump içindeki charset referanslarını değiştir
sed -i 's/CHARSET=latin1/CHARSET=utf8mb4/g' dump.sql
sed -i 's/DEFAULT CHARSET=latin1/DEFAULT CHARSET=utf8mb4/g' dump.sql
sed -i 's/SET NAMES latin1/SET NAMES utf8mb4/g' dump.sql
# Değişiklikleri kontrol et
grep -n "latin1" dump.sql | head -20
Bu yöntem riskli olabilir, özellikle büyük dosyalarda. Her zaman orijinal dosyanın yedeğini alın.
Stored Procedure ve Function’larda Charset
Stored procedure ve function’lar da kendi charset ayarlarına sahip olabilir ve uyumsuzluklara yol açabilir.
-- Mevcut routine'lerin charset bilgisini kontrol et
SELECT routine_name, routine_type, character_set_client,
collation_connection, database_collation
FROM information_schema.routines
WHERE routine_schema = 'veritabani_adi';
-- Routine yeniden oluşturulurken charset belirt
DELIMITER //
CREATE PROCEDURE kullanici_ara(IN arama_terimi VARCHAR(100) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci)
BEGIN
SELECT * FROM kullanicilar
WHERE ad LIKE CONCAT('%', arama_terimi, '%');
END //
DELIMITER ;
Sorun Giderme Kontrol Listesi
Production’da charset/collation sorunu yaşıyorsanız şu adımları sırayla izleyin:
- Önce hata mesajını tam olarak kaydedin, hangi tablo ve kolonların dahil olduğunu tespit edin
SHOW VARIABLES LIKE 'character%'ile sunucu ayarlarını kontrol edininformation_schema.columnssorgusunu çalıştırarak uyumsuz kolonları listeleyin- Uygulama bağlantı string’ini inceleyin, charset parametresi var mı?
- MySQL error log’unu kontrol edin (
/var/log/mysql/error.log) - Hızlı çözüm olarak etkilenen sorguya
COLLATEekleyin - Kalıcı çözüm için maintenance window planlayın ve migration yapın
- Migration sonrası uygulamayı test edin, özellikle Türkçe karakter içeren arama ve sıralama işlemlerini
- Monitoring’e charset/collation uyarıları için alert ekleyin
Yeni Proje Başlarken Doğru Alışkanlıklar
Mevcut sorunları çözmek yerine baştan doğru yapmak çok daha az stres demektir.
-- Yeni veritabanı oluştururken charset belirt
CREATE DATABASE yeni_proje
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- Tablo oluştururken de belirt
CREATE TABLE kullanicilar (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
ad VARCHAR(100) NOT NULL,
soyad VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
biyografi TEXT,
olusturulma_tarihi DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CI/CD pipeline’ınıza charset kontrolü ekleyin. Her migration script’inden önce bir kontrol aşaması koyabilirsiniz:
#!/bin/bash
# charset_kontrol.sh - CI/CD pipeline için
DB_NAME=$1
BEKLENEN_CHARSET="utf8mb4"
BEKLENEN_COLLATION="utf8mb4_unicode_ci"
UYUMSUZ=$(mysql -u root -p"$DB_PASS" -N -e
"SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema='$DB_NAME'
AND character_set_name IS NOT NULL
AND (character_set_name != '$BEKLENEN_CHARSET' OR collation_name != '$BEKLENEN_COLLATION');")
if [ "$UYUMSUZ" -gt "0" ]; then
echo "UYARI: $DB_NAME veritabanında $UYUMSUZ uyumsuz kolon bulundu!"
mysql -u root -p"$DB_PASS" -e
"SELECT table_name, column_name, character_set_name, collation_name
FROM information_schema.columns
WHERE table_schema='$DB_NAME'
AND character_set_name IS NOT NULL
AND (character_set_name != '$BEKLENEN_CHARSET' OR collation_name != '$BEKLENEN_COLLATION');"
exit 1
fi
echo "Charset kontrolü geçti. Tüm kolonlar $BEKLENEN_CHARSET/$BEKLENED_COLLATION kullanıyor."
exit 0
Sonuç
Charset ve collation sorunları göründüğünden daha derin köklere sahip olabilen sorunlardır. Tek bir kolonu düzeltmek yetmez, bütünsel bir yaklaşım gerektirir. Sunucu konfigürasyonu, veritabanı yapısı ve uygulama bağlantıları üçü birden doğru ayarlanmalıdır.
Türkçe karakter desteği için mutlaka utf8mb4 charset’ini kullanın, utf8 ile Türkçe karakterlerde de sorun yaşamasanız bile ileride emoji gibi 4 byte karakterlerle karşılaşabilirsiniz. Collation tercihinde utf8mb4_unicode_ci güvenli bir seçimdir; MySQL 8.0 kullanıyorsanız utf8mb4_0900_ai_ci de değerlendirilebilir.
En önemli tavsiyem şudur: Bu sorunları production baskısı altında değil, önceden planlayarak çözün. Maintenance window’lar, test ortamında doğrulama ve kapsamlı yedekleme bu sürecin vazgeçilmez parçalarıdır. Bir kez doğru konfigüre edilmiş bir veritabanı bu tür sorunlarla sizi uzun süre uğraştırmaz.
