Veritabanı Yönetimi

    utf8mb4 Geçişi: Emoji ve Türkçe Karakter Sorunları

    MySQL tablolarını utf8mb4 karakter setine taşıyıp bozuk Türkçe karakterleri onarmanın yolu.

    11 dk okuma Güncellendi: 25 Ağustos 2026

    Bir müşteri yorumu kaydediliyor, formda emoji var ve veritabanına yazma anında sorgu hata veriyor: Incorrect string value: '\xF0\x9F\x98\x80'. Ya da daha sinsi bir versiyonu: veri kaydediliyor ama sayfada çalışma saatleri gibi bir şey görünüyor. Her iki sorunun kökeninde de aynı şey var — veritabanı gerçek UTF-8 kullanmıyor. MySQL'de utf8mb4 geçişi, bu iki hatayı da kalıcı olarak bitiren adımdır.

    Bu rehberde önce MySQL'in utf8 adını verdiği şeyin neden gerçek UTF-8 olmadığını açıklayacağım; sonra sunucunun mevcut durumunu nasıl tespit edeceğini, sunucu ve bağlantı ayarlarını nasıl değiştireceğini, tabloları güvenli biçimde nasıl dönüştüreceğini göstereceğim. Ardından Türkçe sıralama için hangi collation'ı seçmen gerektiğine ve zaten bozulmuş verinin (mojibake) nasıl kurtarılacağına ayrı bölümler ayırdım. Sonda ise dönüşüm sırasında en çok karşılaşılan indeks uzunluğu hatasının çözümü var.

    utf8 ile utf8mb4 Arasındaki Fark#

    MySQL'in tarihsel bir hatası var: utf8 adını verdiği karakter seti aslında karakter başına en fazla üç bayt saklayabilir. Gerçek UTF-8 ise dört bayta kadar çıkar. Üç bayt, Unicode'un temel çok dilli düzlemini (BMP) kapsar — Türkçe harfler, Yunanca, Kiril, Çince karakterlerin çoğu buraya girer. Emoji, bazı matematiksel semboller ve nadir CJK karakterleri ise dört bayt gerektirir ve bu sete sığmaz.

    MySQL 8'de bu setin gerçek adı utf8mb3 olarak netleştirildi ve kullanımdan kaldırılacağı duyuruldu. utf8mb4 ise gerçek, tam UTF-8'dir. Aradaki farkın pratik sonucu şu tabloda net görünür:

    KarakterBayt sayısıutf8mb3utf8mb4
    a, b, c1SaklanırSaklanır
    ç, ğ, ı, ö, ş, ü2SaklanırSaklanır
    Çince, Japonca (BMP)3SaklanırSaklanır
    Emoji, bayrak, bazı semboller4Hata verirSaklanır

    Dikkat et: Türkçe harfler iki bayt olduğu için utf8mb3 içinde de sorunsuz saklanır. Yani Türkçe karakter bozulması, çoğu zaman utf8mb3 yüzünden değil, bağlantı karakter setinin yanlış olması yüzünden yaşanır — genellikle latin1. Bu iki sorunu birbirinden ayırt etmek, doğru çözümü bulmanın ilk adımıdır. Emoji hatası saf bir karakter seti kapasitesi sorunudur; bozuk Türkçe karakterler ise neredeyse her zaman bağlantı katmanı sorunudur.

    Yeni kurulumlarda MySQL 8 varsayılan olarak utf8mb4 kullanır, dolayısıyla sıfırdan kurulan sistemlerde bu sorun genelde çıkmaz. Sorun, yıllar önce kurulmuş ve sürüm sürüm yükseltilmiş sistemlerde eski ayarların taşınmasından kaynaklanır.

    Mevcut Durumu Tespit Etmek#

    Dönüşüme başlamadan önce nerede durduğunu görmelisin. Üç seviyeyi ayrı ayrı kontrol edeceğiz: sunucu, veritabanı ve tablo/sütun.

    -- 1) Sunucu varsayılanları ve o anki oturumun ayarları
    SHOW VARIABLES LIKE 'character_set%';
    SHOW VARIABLES LIKE 'collation%';
    

    Çıktıda character_set_server, character_set_database, character_set_client, character_set_connection ve character_set_results değerlerine bak. Bunlardan herhangi biri latin1 ya da utf8mb3 ise düzeltilmesi gerekir.

    -- 2) Hangi veritabanları hâlâ eski sette
    SELECT schema_name, default_character_set_name, default_collation_name
    FROM information_schema.schemata
    WHERE default_character_set_name != 'utf8mb4';
    
    -- 3) Hangi sütunlar hâlâ eski sette — asıl liste bu
    SELECT table_schema, table_name, column_name, character_set_name, collation_name, column_type
    FROM information_schema.columns
    WHERE character_set_name IS NOT NULL
      AND character_set_name != 'utf8mb4'
      AND table_schema NOT IN ('mysql','information_schema','performance_schema','sys')
    ORDER BY table_schema, table_name;
    

    Üçüncü sorgu en önemlisidir çünkü karakter seti sütun bazında saklanır. Veritabanının varsayılanını değiştirmek, var olan sütunları değiştirmez; yalnızca sonradan oluşturulacak tabloları etkiler. Bu ayrımı kaçıran çok kişi "veritabanını utf8mb4 yaptım ama hâlâ hata alıyorum" diyor.

    Bir tablonun gerçek durumunu görmenin en hızlı yolu ise şudur:

    SHOW CREATE TABLE magaza.yorum\G
    -- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci
    

    Veritabanı yönetimiyle ilgili temel komutları tazelemek istersen MySQL veritabanı yönetimi yazısı iyi bir başlangıç noktasıdır.

    Sunucu ve Bağlantı Ayarlarını Değiştirmek#

    Sunucu tarafında yapılacak değişiklik yapılandırma dosyasına birkaç satır eklemekten ibarettir. Bu ayarlar yeni oluşturulacak veritabanları ve varsayılan oturum davranışı için geçerlidir.

    # /etc/mysql/mysql.conf.d/mysqld.cnf
    [mysqld]
    character-set-server = utf8mb4
    collation-server     = utf8mb4_unicode_ci
    # İstemci farklı bir set dayatmaya çalışırsa sunucu ayarını koru
    skip-character-set-client-handshake
    
    [client]
    default-character-set = utf8mb4
    
    [mysql]
    default-character-set = utf8mb4
    

    skip-character-set-client-handshake satırı işe yarar bir sigortadır: eski bir istemci kütüphanesi bağlanırken latin1 dayatmaya çalışırsa sunucu bunu yok sayar. Ayarları uyguladıktan sonra servisi yeniden başlat ve doğrula:

    sudo systemctl restart mysql
    mysql -e "SHOW VARIABLES LIKE 'character_set_server';"
    

    Asıl kritik nokta ise uygulama tarafındaki bağlantı karakter setidir. Veritabanı utf8mb4 olsa bile bağlantı latin1 ile açılıyorsa veri yolda bozulur. PHP tarafında bunu bağlantı dizesinde belirtmek en temiz yöntemdir:

    ; PDO bağlantı dizesi — charset parametresi zorunlu
    ; mysql:host=127.0.0.1;dbname=magaza;charset=utf8mb4
    

    WordPress kullanıyorsan wp-config.php dosyasındaki iki sabiti kontrol et: DB_CHARSET değeri utf8mb4, DB_COLLATE ise boş bırakılmalıdır. Başka bir dil veya framework kullanıyorsan, sürücünün bağlantı ayarlarında karakter setini açıkça belirtmen gerekir; her sorgudan önce SET NAMES utf8mb4 göndermek de çalışır ama bağlantı havuzu verimliliğini düşürür ve tercih edilmez.

    Bağlantının gerçekten doğru kurulduğunu şöyle test edersin:

    SELECT @@character_set_client, @@character_set_connection, @@character_set_results;
    -- Üçü de utf8mb4 dönmeli
    

    Tabloları ve Sütunları Dönüştürmek#

    Sunucu ayarları hazır olduğunda sıra mevcut verilerde. Önce veritabanı varsayılanını değiştir, sonra tabloları tek tek dönüştür.

    -- Veritabanı varsayılanı (yeni tabloları etkiler)
    ALTER DATABASE magaza CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    

    Tablo dönüşümünde CONVERT TO CHARACTER SET ifadesi hem tablonun varsayılanını hem de içindeki tüm metin sütunlarını dönüştürür:

    ALTER TABLE magaza.yorum
      CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    

    Çok sayıda tablon varsa komutları elle yazmak yerine üretmek daha pratiktir:

    SELECT CONCAT('ALTER TABLE `', table_schema, '`.`', table_name,
                  '` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;') AS komut
    FROM information_schema.tables
    WHERE table_schema = 'magaza' AND table_type = 'BASE TABLE';
    

    Bu sorgunun çıktısını bir dosyaya alıp gözden geçirdikten sonra çalıştırabilirsin. Ancak büyük tablolarda ALTER TABLE uzun süre kilitleyebilir; üretim ortamında bunu kilitsiz yapmak için Percona Toolkit ile veritabanı bakımı yazısındaki pt-online-schema-change aracını kullanmalısın:

    pt-online-schema-change \
      --alter "CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" \
      D=magaza,t=yorum --max-load="Threads_running=40" --execute
    

    Dönüşüm sırasında karşılaşacağın en yaygın hata şudur:

    ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
    

    Bunun sebebi şu: VARCHAR(255) bir sütun utf8mb3 ile en fazla 765 bayt yer kaplarken, utf8mb4 ile 1020 bayta çıkar. Eski COMPACT satır formatındaki InnoDB tablolarında indeks anahtarı 767 bayt ile sınırlıdır, dolayısıyla sığmaz. Çözüm satır formatını DYNAMIC yapmaktır:

    -- Sunucu genelinde varsayılanı ayarla
    SET GLOBAL innodb_default_row_format = DYNAMIC;
    
    -- Sorunlu tabloyu dönüştür
    ALTER TABLE magaza.yorum ROW_FORMAT=DYNAMIC;
    ALTER TABLE magaza.yorum CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    

    Alternatif çözüm, indekslenen sütunun uzunluğunu düşürmektir; VARCHAR(255) yerine VARCHAR(191) kullanmak eski WordPress sürümlerinin tercih ettiği yöntemdi. Modern sistemlerde DYNAMIC formatı doğru çözümdür, uzunluk kısaltmaya gerek yoktur.

    Türkçe Sıralama ve Doğru Collation Seçimi#

    Karakter seti hangi karakterlerin saklanabileceğini belirler; collation ise nasıl karşılaştırılıp sıralanacaklarını. Türkçe içerikte bu ikinci karar gözden kaçar ve garip sonuçlar doğurur.

    CollationDavranışNe zaman
    utf8mb4_general_ciHızlı ama kaba, bazı eşleşmeleri yanlış yaparYeni projede kullanma
    utf8mb4_unicode_ciUnicode kurallarına uygun, çok dilli içerik için dengeliGenel amaçlı varsayılan
    utf8mb4_0900_ai_ciMySQL 8 varsayılanı, güncel Unicode sürümü, hızlıMySQL 8 projeleri
    utf8mb4_turkish_cii/ı ve İ/I ayrımını Türkçe kurallarına göre yaparTürkçe sıralama kritikse
    utf8mb4_binBayt bazlı, büyük/küçük harf duyarlıToken, hash, kod alanları

    Türkçe'ye özgü asıl mesele noktasız ı ve noktalı i harfleridir. Genel collation'larda i ile I aynı harf kabul edilir, oysa Türkçe'de I harfinin küçüğü ı, i harfinin büyüğü İ'dir. Bir kullanıcı adı alanında bu fark güvenlik sonucu bile doğurabilir; utf8mb4_turkish_ci bu ayrımı doğru yapar.

    -- Sadece sıralamanın kritik olduğu sütunda Türkçe collation kullan
    ALTER TABLE magaza.musteri
      MODIFY ad VARCHAR(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_turkish_ci;
    
    -- Tek seferlik sıralama için sorgu içinde de belirtebilirsin
    SELECT ad FROM magaza.musteri ORDER BY ad COLLATE utf8mb4_turkish_ci;
    

    Bir uyarı: farklı collation'lara sahip iki sütunu birleştirmeye (JOIN) kalkarsan Illegal mix of collations hatası alırsın. Bu yüzden Türkçe collation'ı gelişigüzel değil, yalnızca gerçekten sıralama gerektiren sütunlarda kullan ve o sütunla eşleşecek diğer sütunları da aynı collation'a getir.

    Bozuk Karakterleri (Mojibake) Onarmak#

    Şimdi en can sıkıcı senaryoya gelelim: veriler zaten bozuk kaydedilmiş. Ekranda çalışma yerine çalışma görüyorsun. Bu, çift kodlama denen durumdur: uygulama UTF-8 baytları göndermiş, MySQL bunları latin1 sanmış ve her baytı ayrı bir karakter olarak yeniden kodlamıştır.

    İyi haber şu: bu işlem tersine çevrilebilir, çünkü orijinal baytlar kaybolmamış, yalnızca yanlış yorumlanmıştır. Kötü haber ise dönüşümün riskli olması — başlamadan önce mutlaka tam yedek al.

    Yöntem, sütunu önce ikili (binary) türe çevirip yorumlamayı sıfırlamak, sonra doğru karakter setiyle geri çevirmektir:

    -- Önce yedek! Sonra sırasıyla:
    ALTER TABLE magaza.yorum MODIFY icerik BLOB;
    ALTER TABLE magaza.yorum MODIFY icerik TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    

    VARCHAR sütunlar için ara tür VARBINARY olur:

    ALTER TABLE magaza.musteri MODIFY ad VARBINARY(120);
    ALTER TABLE magaza.musteri MODIFY ad VARCHAR(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    

    Dönüşümden önce mutlaka birkaç satırda prova yap. Aşağıdaki sorgu, bozuk bir metnin düzeltilmiş hâlini veriyi değiştirmeden gösterir:

    SELECT ad,
           CONVERT(CAST(CONVERT(ad USING latin1) AS BINARY) USING utf8mb4) AS duzeltilmis
    FROM magaza.musteri LIMIT 10;
    

    duzeltilmis sütunu doğru Türkçe gösteriyorsa yöntem işe yarayacak demektir. Eğer bu sorgu da bozuk çıkıyorsa veri farklı bir şekilde bozulmuş olabilir; o durumda bir dump alıp metin düzeyinde incelemek gerekir. Dışa aktarma sırasında karakter setini açıkça belirtmeyi unutma:

    mysqldump --default-character-set=utf8mb4 --single-transaction magaza > /yedek/magaza.sql
    file /yedek/magaza.sql   # UTF-8 Unicode text çıkmalı
    

    phpMyAdmin üzerinden dışa aktarım yapıyorsan karakter seti seçeneğini kontrol etmen şart; ayrıntılar için phpMyAdmin içe ve dışa aktarma yazısına bakabilirsin.

    Sık Yapılan Hatalar ve Kontrol Listesi#

    Birinci hata, yalnızca veritabanı varsayılanını değiştirip iş bitti sanmaktır. ALTER DATABASE var olan tablolara dokunmaz; sütun bazındaki karakter setleri olduğu gibi kalır. Dönüşümün gerçekten tamamlandığını information_schema.columns sorgusuyla doğrula, tek satır bile dönmemeli.

    İkinci hata, bağlantı karakter setini atlamaktır. Sunucu, veritabanı ve tabloların hepsi utf8mb4 olsa bile uygulama latin1 ile bağlanıyorsa veri yine bozulur. Zincirin dört halkası var: sunucu, veritabanı, tablo/sütun ve bağlantı. Dördü de doğru olmadan sorun bitmez.

    Üçüncü hata, dönüşümü yedeksiz yapmaktır. Özellikle mojibake onarımı geri dönüşü olmayan bir işlemdir; ara adımda bir yanlışlık olursa veriyi kurtarmanın tek yolu yedektir. Büyük bir dönüşüme başlamadan önce tam yedek almak ve mümkünse önce bir kopyada denemek şarttır.

    Dördüncü hata, indeks uzunluğu hatasını VARCHAR(191) ile geçiştirip nedenini anlamamaktır. Kısaltma çalışır ama alan sınırını gereksiz yere daraltır; doğru çözüm ROW_FORMAT=DYNAMIC kullanmaktır ve modern MySQL sürümlerinde bu zaten varsayılandır.

    Beşinci hata, LENGTH() ile CHAR_LENGTH() fonksiyonlarını karıştırmaktır. utf8mb4'te bir Türkçe harf iki bayt, bir emoji dört bayttır; LENGTH() bayt sayar, CHAR_LENGTH() karakter sayar. Uygulamada karakter sınırı kontrolü yapıyorsan doğru fonksiyon CHAR_LENGTH() olmalıdır:

    SELECT LENGTH('çalışma') AS bayt, CHAR_LENGTH('çalışma') AS karakter;
    -- bayt: 10   karakter: 7
    

    Son olarak, dönüşüm sonrası indeks boyutlarının bir miktar büyüyeceğini hesaba kat. utf8mb4, indekslerde karakter başına dört bayt varsayar; çok sütunlu indekslerin olduğu büyük tablolarda disk kullanımı artabilir. Performans etkilerini gözlemlemek için MySQL ve MariaDB performans optimizasyonu yazısındaki tampon havuzu önerilerine göz atmanı öneririm.

    Sıkça Sorulan Sorular#

    utf8mb4 geçişi verimi düşürür mü#

    Belirgin bir düşüş beklenmez ama indeksler biraz büyür, çünkü utf8mb4 karakter başına dört bayta kadar yer ayırabilir. Bu artış çoğunlukla yüzde birkaç seviyesinde kalır ve modern donanımda hissedilmez. Buna karşılık indekslenen çok uzun metin sütunların varsa toplam indeks boyutu gözle görülür şekilde artabilir; bu tür sütunlarda indeks uzunluğunu sınırlamak iyi bir tedbirdir.

    Emoji hatası alıyorum ama Türkçe karakterlerim düzgün, neden#

    Çünkü bunlar iki farklı sorundur. Türkçe harfler iki bayt olduğu için eski utf8mb3 setine sığar ve düzgün saklanır; emoji ise dört bayt gerektirdiği için sığmaz ve Incorrect string value hatası verir. Yani Türkçe karakterlerin çalışıyor olması karakter setinin doğru olduğu anlamına gelmez, yalnızca kapasitenin şimdilik yettiği anlamına gelir.

    Hangi collation'ı seçmeliyim#

    Genel amaçlı bir uygulamada utf8mb4_unicode_ci güvenli ve dengeli bir tercihtir; MySQL 8 kullanıyorsan varsayılan olan utf8mb4_0900_ai_ci daha güncel Unicode kurallarını uygular ve biraz daha hızlıdır. Türkçe'ye özgü i ve ı ayrımının sıralama ya da karşılaştırmada önemli olduğu sütunlarda utf8mb4_turkish_ci kullanmalısın. Eski utf8mb4_general_ci seçeneğini yeni projelerde tercih etme.

    Dönüşüm ne kadar sürer ve site kapalı kalır mı#

    Süre tablo boyutuna bağlıdır; küçük tablolar saniyeler içinde dönüşürken milyonlarca satırlık bir tablo dakikalar hatta saatler alabilir. Düz ALTER TABLE komutu bu süre boyunca tabloyu kilitler, yani site o tabloya yazamaz. Kesintisiz dönüşüm için pt-online-schema-change aracını kullanabilirsin; tabloyu arka planda kopyalayarak dönüştürür ve uygulama yazmaya devam eder.

    Bozuk kaydedilmiş verileri geri getirebilir miyim#

    Çoğu durumda evet. Çift kodlama sonucu bozulan veride orijinal baytlar kaybolmamış, sadece yanlış yorumlanmıştır; sütunu önce ikili türe çevirip sonra doğru karakter setine geri çevirerek düzeltilebilir. Ancak bu işlem geri alınamaz, bu yüzden mutlaka önce tam yedek al ve düzeltmeyi bir kopyada dene. Veri farklı bir şekilde bozulduysa bu yöntem sonuç vermeyebilir.

    WordPress veritabanımı utf8mb4'e nasıl çeviririm#

    Önce wp-config.php dosyasında DB_CHARSET değerini utf8mb4 yap ve DB_COLLATE satırını boş bırak. Ardından veritabanı varsayılanını ve tüm tabloları CONVERT TO CHARACTER SET utf8mb4 ile dönüştür. Modern WordPress sürümleri utf8mb4 destekli olduğu için ek bir eklenti gerekmez; sadece dönüşüm öncesi tam yedek almayı ve indeks uzunluğu hatası çıkarsa tabloları ROW_FORMAT=DYNAMIC yapmayı unutma.

    Kapanış#

    utf8mb4 geçişi, bir kez doğru yapıldığında bir daha dönüp bakmayacağın türden bir iştir; ama yarım yapıldığında yıllarca peşini bırakmaz. Aklında tutman gereken dört alışkanlık şu: zincirin dört halkasını da (sunucu, veritabanı, sütun, bağlantı) ayrı ayrı doğrula, information_schema.columns sorgusunun boş dönmesini nihai kanıt say, dönüşümden önce mutlaka tam yedek al ve indeks uzunluğu hatasını uzunluk kısaltarak değil ROW_FORMAT=DYNAMIC ile çöz. Türkçe sıralamanın kritik olduğu sütunlarda utf8mb4_turkish_ci seçmeyi de not et.

    Bu dönüşümü kendi sunucunda rahatça denemek istiyorsan tam root erişimi veren VDS paketlerimiz uygun bir ortam sunar; paylaşımlı barındırmada çalışıyorsan cPanel ve phpMyAdmin erişimi olan web hosting ve WordPress hosting paketlerimizle aynı adımları panel üzerinden uygulayabilirsin. Dönüşümü ve öncesindeki yedeklemeyi bize bırakmak istersen site taşıma ve sunucu yönetimi hizmetlerimiz bu işi üstlenir.

    utf8mb4Karakter SetiMySQL

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.