MySQL ve MariaDB’de Karakter Seti ve Collation Yönetimi: utf8mb4 Geçişi ve Encoding Sorunlarını Çözme

Veritabanı yönetiminde en çok baş ağrıtan konulardan biri karakter seti sorunlarıdır. Türkçe karakterlerin bozuk görünmesi, emoji’lerin kaydedilememesi ya da farklı uygulamalar arasında veri taşırken yaşanan encoding karmaşası… Bunları yaşadıysanız doğru yerdesiniz. Bu yazıda MySQL ve MariaDB’de karakter seti yönetimini, özellikle utf8mb4 geçişini ve encoding sorunlarını pratik bir bakış açısıyla ele alacağız.

Karakter Seti ve Collation Nedir?

Önce temel kavramları netleştirelim. Karakter seti (character set), veritabanında hangi karakterlerin saklanabileceğini ve bunların nasıl kodlanacağını belirler. Collation ise bu karakterlerin sıralanma ve karşılaştırma kurallarını tanımlar.

Örneğin utf8_general_ci collation’ında “ci” büyük/küçük harf duyarsız (case-insensitive) anlamına gelir. “türkçe” ile “TÜRKÇE” bu collation ile arama yapıldığında eşit kabul edilir. Ama Türkçe için önemli olan nokta, “i” ve “İ” karakterlerinin doğru karşılaştırılmasıdır. Yanlış collation seçimi Türkçe büyük/küçük harf dönüşümlerinde ciddi sorunlar yaratır.

MySQL’in “utf8” Tuzağı

Burada pek çok sysadmin’in düştüğü klasik bir tuzak var. MySQL’de utf8 olarak geçen karakter seti aslında gerçek UTF-8 değildir. MySQL’in utf8 implementasyonu karakter başına maksimum 3 byte kullanır. Oysa gerçek UTF-8 standardı karakter başına 4 byte’a kadar çıkabilir.

Bu ne anlama geliyor? 4 byte gerektiren karakterler, yani emoji’ler (😀, ❤️) ve bazı özel Çince-Japonca karakterler MySQL’in utf8 kolonlarına kaydedilemez. Uygulama bu karakterleri kaydetmeye çalıştığında ya sessizce kesilir ya da hata alırsınız.

utf8mb4 tam olarak bu problemi çözmek için geliştirilmiştir. “mb4” kısmı “multi-byte 4” anlamına gelir ve gerçek 4 byte UTF-8 desteği sunar. MySQL 5.5.3 ve üzeri, MariaDB 5.5 ve üzeri sürümlerde mevcuttur.

Mevcut Durumu Kontrol Etmek

Geçişe başlamadan önce sisteminizin mevcut durumunu tam olarak anlamalısınız. Aşağıdaki sorgular size kapsamlı bir tablo sunacak:

mysql -u root -p -e "SHOW VARIABLES LIKE 'character%'; SHOW VARIABLES LIKE 'collation%';"

Bu komutun çıktısında şunlara bakın:

  • character_set_client: İstemcinin MySQL’e gönderdiği verinin kodlaması
  • character_set_connection: Bağlantı katmanında kullanılan kodlama
  • character_set_database: Varsayılan veritabanının karakter seti
  • character_set_results: MySQL’in istemciye döndürdüğü sonuçların kodlaması
  • character_set_server: Sunucu düzeyinde varsayılan karakter seti
  • collation_connection: Bağlantı collation’ı
  • collation_database: Veritabanı collation’ı
  • collation_server: Sunucu düzeyi collation

Belirli bir veritabanındaki tablo ve kolon bilgilerini görmek için:

mysql -u root -p your_database -e "
SELECT 
    TABLE_NAME, 
    TABLE_COLLATION 
FROM information_schema.TABLES 
WHERE TABLE_SCHEMA = 'your_database';

SELECT 
    TABLE_NAME,
    COLUMN_NAME,
    CHARACTER_SET_NAME,
    COLLATION_NAME,
    COLUMN_TYPE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'your_database'
AND CHARACTER_SET_NAME IS NOT NULL
ORDER BY TABLE_NAME, COLUMN_NAME;
"

MySQL/MariaDB Yapılandırma Dosyasını Güncellemek

Sunucu düzeyinde utf8mb4 varsayılan yapmak için /etc/mysql/mysql.conf.d/mysqld.cnf veya /etc/my.cnf dosyanızı düzenlemeniz gerekir. Dağıtıma göre konum değişebilir:

# Yapılandırma dosyasını bulmak için:
mysql --help | grep "Default options" -A 1
# Ya da:
find /etc -name "my.cnf" -o -name "mysqld.cnf" 2>/dev/null

Yapılandırma dosyasına eklenecek ayarlar:

# /etc/mysql/mysql.conf.d/mysqld.cnf veya /etc/my.cnf

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
character-set-filesystem = binary
skip-character-set-client-handshake

[client]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

[mysqldump]
default-character-set = utf8mb4

skip-character-set-client-handshake direktifi önemlidir; istemcinin farklı bir karakter seti talep etmesini engelleyerek sunucu ayarını zorlar.

Yapılandırmayı uygulamak için servisi yeniden başlatın:

# systemd kullanan sistemler için:
sudo systemctl restart mysql
# veya MariaDB için:
sudo systemctl restart mariadb

# Değişikliği doğrulayın:
mysql -u root -p -e "SHOW VARIABLES LIKE 'character_set_server'; SHOW VARIABLES LIKE 'collation_server';"

Mevcut Veritabanı ve Tabloları Dönüştürmek

Sunucu yapılandırmasını değiştirmek yeni oluşturulacak veritabanlarını etkiler; mevcut olanları değiştirmez. Bunun için her veritabanı ve tablo üzerinde dönüşüm yapmanız gerekir.

Önce yedek alın! Bu adımı atlamayın:

# Tam yedek
mysqldump -u root -p 
  --default-character-set=utf8mb4 
  --hex-blob 
  --single-transaction 
  --routines 
  --triggers 
  --all-databases > /backup/full_backup_$(date +%Y%m%d_%H%M%S).sql

# Sadece belirli bir veritabanı
mysqldump -u root -p 
  --default-character-set=utf8mb4 
  --hex-blob 
  --single-transaction 
  your_database > /backup/your_database_$(date +%Y%m%d_%H%M%S).sql

Şimdi dönüşüm için bir script hazırlayalım. Bu script hem veritabanını hem de tüm tablolarını dönüştürür:

#!/bin/bash
# utf8mb4_migration.sh
# Kullanim: ./utf8mb4_migration.sh veritabani_adi

DB_NAME="$1"
DB_USER="root"
DB_PASS="sifreniz"

if [ -z "$DB_NAME" ]; then
    echo "Kullanim: $0 <veritabani_adi>"
    exit 1
fi

echo "=== $DB_NAME icin utf8mb4 gecisi basliyor ==="

# Veritabanini donustur
mysql -u "$DB_USER" -p"$DB_PASS" -e 
    "ALTER DATABASE `$DB_NAME` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

echo "Veritabani karakter seti guncellendi."

# Tum tablolari donustur
TABLES=$(mysql -u "$DB_USER" -p"$DB_PASS" -N -e 
    "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA='$DB_NAME';")

for TABLE in $TABLES; do
    echo "Tablo donusturuluyor: $TABLE"
    mysql -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -e 
        "ALTER TABLE `$TABLE` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
    
    if [ $? -eq 0 ]; then
        echo "  OK: $TABLE"
    else
        echo "  HATA: $TABLE donusturulemedi!"
    fi
done

echo "=== Gecis tamamlandi ==="

# Sonucu dogrula
mysql -u "$DB_USER" -p"$DB_PASS" -e 
    "SELECT TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA='$DB_NAME';"

Script’i çalıştırın:

chmod +x utf8mb4_migration.sh
./utf8mb4_migration.sh your_database

Collation Seçimi: unicode_ci mi, general_ci mi?

utf8mb4 geçişinde sık sorulan sorulardan biri collation seçimidir.

  • utf8mb4_general_ci: Daha hızlıdır ancak bazı Unicode kurallarını basitleştirir. Türkçe “ı” ve “i” gibi özel karakterlerde beklenmedik sonuçlar verebilir.
  • utf8mb4_unicode_ci: Unicode standartlarına tam uyum sağlar. Türkçe dahil pek çok dil için daha doğru sıralama ve karşılaştırma yapar.
  • utf8mb4_turkish_ci: Türkçeye özgü sıralama kuralları içerir. “ç, ğ, ı, ö, ş, ü” gibi karakterlerin Türkçe alfabetik sıralamasında işlenmesini sağlar.
  • utf8mb4_0900_ai_ci: MySQL 8.0 ile gelen daha modern bir collation. “ai” accent-insensitive, “ci” case-insensitive demektir.

Türkçe içerik yoğun uygulamalar için utf8mb4_turkish_ci ya da utf8mb4_unicode_ci önerilir. Ancak collation değiştirme indeks yeniden oluşturmayı tetikler; büyük tablolarda bu işlem zaman alabilir.

Gerçek Dünya Senaryosu: PHP Uygulaması ile Bağlantı

Bir PHP uygulamasının MySQL’e bağlanırken encoding sorunuyla karşılaştığınızı düşünelim. Uygulama Türkçe karakterleri bozuk kaydediyor. Yapılandırma tarafını düzelttiniz ama sorun devam ediyor. Problem büyük ihtimalle uygulama bağlantı ayarlarındadır.

PDO ile bağlantı için:

# php_config_test.php - test dosyasi
php -r "
$pdo = new PDO(
    'mysql:host=localhost;dbname=test_db;charset=utf8mb4',
    'kullanici',
    'sifre',
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci',
        PDO::MYSQL_ATTR_CHARSET => 'utf8mb4'
    ]
);

$stmt = $pdo->query('SHOW VARIABLES LIKE "character_set%"');
while ($row = $stmt->fetch()) {
    echo $row[0] . ' = ' . $row[1] . PHP_EOL;
}
echo 'Baglanti basarili!' . PHP_EOL;
"

mysqli kullanan eski uygulamalar için yapılandırma dosyasına şu satırlar eklenebilir:

# Baglanti acildiktan hemen sonra calistirilmasi gereken komutlar
# mysqli_set_charset($conn, 'utf8mb4');
# ya da sorgu olarak:
# SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';

# Bunu shell uzerinden test edebilirsiniz:
mysql -u kullanici -p test_db -e "
SET NAMES 'utf8mb4';
INSERT INTO test_tablo (metin) VALUES ('Merhaba Dünya 😀 Türkçe: şğıöüç');
SELECT * FROM test_tablo ORDER BY id DESC LIMIT 1;
"

Encoding Sorunlarını Teşhis Etmek

Bozuk veri mi var yoksa sadece görüntüleme sorunu mu? Bunu ayırt etmek önemlidir.

# Hex ciktisiyla gercek byte degerlerini gormek
mysql -u root -p your_database -e "
SELECT 
    metin_kolonu,
    HEX(metin_kolonu) as hex_deger,
    CHAR_LENGTH(metin_kolonu) as karakter_sayisi,
    LENGTH(metin_kolonu) as byte_sayisi
FROM test_tablo
WHERE id = 1;
"

Eğer bir karakter UTF-8’de doğru saklanıyorsa, CHAR_LENGTH ile LENGTH değerleri farklı olabilir (çünkü bazı karakterler 2-4 byte tutar). Ama eğer çift kodlama (double encoding) sorunu varsa, byte sayısı beklenenden çok daha fazla çıkar.

Çift kodlama tespiti ve düzeltmesi gerçek dünyada sık karşılaşılan bir senaryodur. Veriler önce latin1 olarak okunup sonra utf8 olarak kaydedildiyse bu sorun oluşur:

# Cift kodlanmis veriyi duzeltmek icin:
mysql -u root -p your_database -e "
UPDATE kullanicilar 
SET ad = CONVERT(BINARY CONVERT(ad USING latin1) USING utf8mb4)
WHERE ad RLIKE '[\xC3\xC4\xC5\xC6\xC7\xC8\xC9]';
"

Bu sorguyu çalıştırmadan önce mutlaka test edin ve sadece etkilenen satırlara uygulayın.

MariaDB’ye Özgü Notlar

MariaDB büyük ölçüde MySQL ile uyumludur ancak birkaç farklılık var. MariaDB 10.2 ve üzerinde utf8mb4 varsayılan olarak daha iyi desteklenir. MariaDB 10.6 ile birlikte utf8 ve utf8mb4 birleştirilmiş; yani utf8 artık gerçek utf8mb4 anlamına geliyor.

# MariaDB versiyonunu kontrol edin
mysql -u root -p -e "SELECT VERSION();"

# MariaDB 10.6+ icin karakter seti durumu
mysql -u root -p -e "SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'version%';"

# MariaDB icin my.cnf ornegi
# /etc/mysql/mariadb.conf.d/50-server.cnf
cat << 'EOF' | sudo tee -a /etc/mysql/mariadb.conf.d/50-server.cnf
[mysqld]
character-set-server  = utf8mb4
collation-server      = utf8mb4_unicode_ci

[client-server]
default-character-set = utf8mb4
EOF

sudo systemctl restart mariadb

İndeks Uzunluğu Sorunları

utf8mb4 geçişinin en can sıkıcı taraflarından biri indeks uzunluğu limitidir. MySQL 5.6 ve altında, InnoDB varsayılan olarak satır başına maksimum 767 byte indeks uzunluğuna izin verir. utf8 ile VARCHAR(255) bir kolonu indeksleyebilirsiniz (255 x 3 = 765 byte). Ama utf8mb4 ile bu 255 x 4 = 1020 byte eder ve limite takılırsınız.

# Cozum 1: innodb_large_prefix aktif etmek (MySQL 5.6-5.7)
# my.cnf dosyasina ekleyin:
# innodb_large_prefix = ON
# innodb_file_format = Barracuda
# innodb_file_per_table = ON

# Cozum 2: Indeks uzunlugunu kisaltmak
mysql -u root -p your_database -e "
ALTER TABLE kullanicilar 
DROP INDEX email_idx;

ALTER TABLE kullanicilar 
ADD INDEX email_idx (email(191));
"
# 191 * 4 = 764 byte, limitin altinda

# Cozum 3: MySQL 5.7+ ve MariaDB 10.3+ icin dinamik row format
mysql -u root -p your_database -e "
ALTER TABLE kullanicilar 
ROW_FORMAT=DYNAMIC;
"

Sonuç

utf8mb4 geçişi kulağa karmaşık gelse de sistematik bir yaklaşımla sorunsuz tamamlanabilir. Özetlemek gerekirse:

  • MySQL’in utf8‘i gerçek UTF-8 değildir; utf8mb4 kullanın
  • Geçiş öncesi mutlaka yedek alın
  • Önce sunucu yapılandırmasını güncelleyin
  • Mevcut veritabanı, tablo ve kolonları dönüştürün
  • Uygulama bağlantı ayarlarını gözden geçirin
  • Türkçe projeler için utf8mb4_turkish_ci ya da utf8mb4_unicode_ci tercih edin
  • İndeks uzunluğu sorunlarına hazırlıklı olun

Bu adımları takip ettiğinizde bozuk Türkçe karakterler, kayıp emoji’ler ve encoding uyumsuzlukları tarih olacak. Üretim ortamında bu işlemleri yapmadan önce mutlaka test ortamında deneyin ve bakım penceresi planlayın. Büyük tablolarda CONVERT TO CHARACTER SET işlemi tablo kilidine neden olabileceğinden, yoğun saatlerde çalıştırmaktan kaçının.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir