- Katılım
- 21 May 2023
- Mesajlar
- 505
- Tepki
- 17
- Puan
- 18
Admin panelinde bir kayda bakıyorsunuz: isim alanında "ÇaÄŸlayan" ya da "Kadıköy" gibi bir dizi görünüyor. Aynı kaydı e-posta bildirimine koyduğunuzda Türkçe harflerin yerinde düz soru işaretleri çıkıyor. İki ekran, iki farklı bozulma türü — çoğu zaman veri tabanında "bozuk" bir şey yoktur, zincirin bir halkası baytları yanlış yorumlamıştır.
Doğru sıra şu: önce bozulmanın türünü ayırt edip (kayıp mı, yanlış okuma mı), sonra zinciri — dosya, çıktı başlığı, bağlantı, sütun — tek tek sınamak gerekir. Rastgele "hepsini utf8mb4 yap" denemesi bazen zaten doğru olan metni ikinci kez bozar.
Bir metin dosyadan çıkıp ekrana gelene kadar birkaç kez "bu baytlar hangi karaktere karşılık geliyor" sorusuna cevap verilir: kaynak dosyanın kodlaması, uygulama çıktısının kodlaması, HTTP başlığı veya HTML içindeki bildirim, veri tabanı bağlantısının kodlaması ve sütunun kendi karakter kümesi. Bu halkalardan biri farklı bir varsayım yaparsa aynı bayt dizisi başka yerde başka bir şeye dönüşür. "Veri tabanı bozuldu" diye tek bir yeri suçlamadan önce hangi halkanın farklı düşündüğünü bulmak gerekir.
Üç görüntü farklı şeyler anlatır. Düz "?" genellikle bir dönüştürücünün karakteri hedef kodlamada temsil edemediği için onu tamamen attığı anlamına gelir; bu çoğunlukla geri döndürülemez bir kayıptır. Kare içindeki işaret genelde Unicode'un REPLACEMENT CHARACTER'ıdır (U+FFFD): çözücü baytı hiçbir karaktere eşleyemediğinde bunu koyar, o da kalıcı bir kayıp sinyalidir. "Ç", "ö" gibi Latin harf dizileri ise UTF-8 baytlarının tek baytlık bir kodlamayla (Latin-1/CP1252 ailesi) okunmasıdır; baytlar kaybolmadığı için çoğu durumda geri çevrilebilir. Python'ın resmî Unicode HOWTO'su, veriyi olabildiğince erken çözüp en sona kadar metin olarak taşımanın doğru yol olduğunu, aksi halde
Bunu kendi ortamımda denedim: Türkçe bir cümleyi UTF-8 baytına çevirip aynı baytları Latin-1 olarak okuttum.
Gerçek çıktı:
Her halkada bakılacak nokta farklıdır:
WHATWG HTML Living Standard'ın karakter kodlaması bölümü,
MySQL 8.4'ün utf8mb4 belgesi, BMP karakterleri için
Bağlantı tarafında dört sistem değişkeni var:
Türkçe İ/ı için aynı belge, collation isimlendirmesinde ayrı bir "tr"/"turkish" dil belirteci bulunduğunu doğruluyor. Dotted/dotless I'nın büyük-küçük harf eşleşmesini tam olarak nasıl etkilediği incelediğim sayfalarda açıkça yazmıyordu; bunu kesin kural saymak yerine kritik bir arama senaryosunda
Yukarıdaki çözme/geri çevirme denemesi yalnızca gerçekten "UTF-8 baytları latin1 gibi okunmuş" satırlarda işe yarar. Zaten doğru görünen bir satırı aynı script'ten geçirirseniz bir kez daha yanlış kodlarsınız; bu, mojibake'i düzeltmek yerine ikinci bir bozulma katmanı ekler. Toplu script öncesi tablonun yedeğini alın, script'i önce birkaç satırlık küçük bir örnekte çalıştırıp satır satır karşılaştırın. Site taşıma rehberi, dışa/içe aktarmada dump dosyasının kodlamasının da zincire eklenen ayrı bir halka olduğunu hatırlatan iyi bir tamamlayıcı.
Yeni bir projede zinciri baştan tek kodlamaya sabitlemek, sonradan onarmaktan kolaydır: dosyaları BOM'suz UTF-8 kaydedin, çıktıda
"?" bir kayba, kare işareti çözülemeyen bir bayta, "Ç"/"ö" tipi dizi ise genelde geri çevrilebilir bir yanlış okumaya işaret eder. Zinciri dosyadan sütuna tek tek sınamadan hiçbir halkayı suçlamayın; toplu düzeltmeden önce mutlaka yedek alıp küçük bir örnekte deneyin. Sizde bozulma en son zincirin hangi halkasında çıktı: dosyada mı, bağlantıda mı, tabloda mı?
Güncelleme: 21 Ağustos 2026. MySQL 8.4 Reference Manual, WHATWG HTML Living Standard (meta charset) ve Python Unicode HOWTO resmî belgeleri kontrol edildi.
Doğru sıra şu: önce bozulmanın türünü ayırt edip (kayıp mı, yanlış okuma mı), sonra zinciri — dosya, çıktı başlığı, bağlantı, sütun — tek tek sınamak gerekir. Rastgele "hepsini utf8mb4 yap" denemesi bazen zaten doğru olan metni ikinci kez bozar.
Aynı veri iki ekranda farklı görünüyorsa sorun genelde depolama değil, yorumlamadır
Bir metin dosyadan çıkıp ekrana gelene kadar birkaç kez "bu baytlar hangi karaktere karşılık geliyor" sorusuna cevap verilir: kaynak dosyanın kodlaması, uygulama çıktısının kodlaması, HTTP başlığı veya HTML içindeki bildirim, veri tabanı bağlantısının kodlaması ve sütunun kendi karakter kümesi. Bu halkalardan biri farklı bir varsayım yaparsa aynı bayt dizisi başka yerde başka bir şeye dönüşür. "Veri tabanı bozuldu" diye tek bir yeri suçlamadan önce hangi halkanın farklı düşündüğünü bulmak gerekir.
Bozulmanın türünü okumak: "?" mi, "Ö" mü, kare mi
Üç görüntü farklı şeyler anlatır. Düz "?" genellikle bir dönüştürücünün karakteri hedef kodlamada temsil edemediği için onu tamamen attığı anlamına gelir; bu çoğunlukla geri döndürülemez bir kayıptır. Kare içindeki işaret genelde Unicode'un REPLACEMENT CHARACTER'ıdır (U+FFFD): çözücü baytı hiçbir karaktere eşleyemediğinde bunu koyar, o da kalıcı bir kayıp sinyalidir. "Ç", "ö" gibi Latin harf dizileri ise UTF-8 baytlarının tek baytlık bir kodlamayla (Latin-1/CP1252 ailesi) okunmasıdır; baytlar kaybolmadığı için çoğu durumda geri çevrilebilir. Python'ın resmî Unicode HOWTO'su, veriyi olabildiğince erken çözüp en sona kadar metin olarak taşımanın doğru yol olduğunu, aksi halde
UnicodeDecodeError veya sessiz kayıp çıkabileceğini anlatıyor.Bunu kendi ortamımda denedim: Türkçe bir cümleyi UTF-8 baytına çevirip aynı baytları Latin-1 olarak okuttum.
Kod:
s = "İstanbul'da öğle güneşi çok sıcaktı, çünkü ışık kırılıyordu."
mojibake = s.encode("utf-8").decode("latin1")
geri = mojibake.encode("latin1").decode("utf-8")
Gerçek çıktı:
mojibake İstanbul'da öÄle güneÅi çok sıcaktı... oldu, geri == s ise True döndü — tersine çevirme çalıştı. Bir ayrıntı: "ğ"/"ş" harflerinin ikinci baytı Latin-1'de görünmez bir kontrol koduna denk geldiği için harf ekrandan kayboluyor, sadece "Ä"/"Å" kalıyor. Aynı metni Latin-1'e kodlamaya zorlayınca Python "İ" için UnicodeEncodeError fırlattı — hedefte karşılığı olmayan karakter ya hataya düşer ya da "?" ile değiştirilir.Zinciri sınamak: dosya → çıktı başlığı → bağlantı → sütun
Her halkada bakılacak nokta farklıdır:
- Kaynak dosya: Editörün hangi kodlamayla kaydettiği ve baştaki bir BOM olup olmadığı.
- Uygulama çıktısı:
Content-Type: ...; charset=utf-8başlığı veya HTML içindeki meta bildirimi. - Bağlantı: Uygulamanın veri tabanına hangi kodlamayla konuştuğu.
- Sütun/tablo: Şemanın kendi karakter kümesi ve collation'ı.
WHATWG HTML Living Standard'ın karakter kodlaması bölümü,
meta öğesinin charset özniteliğinin "utf-8" ile büyük/küçük harf duyarsız eşleşmesi gerektiğini ve bir belgede birden fazla charset'li meta olamayacağını belirtiyor. PHP'de mbstring de aynı amaca hizmet eder: substr gibi tek baytlık varsayan fonksiyonları çok baytlı karakteri bozmadan çalıştırır.Veri tabanı tarafı: utf8mb4, bağlantı kodlaması ve Türkçe collation
MySQL 8.4'ün utf8mb4 belgesi, BMP karakterleri için
utf8mb4 ile eski utf8mb3'ün aynı saklandığını, ama ek düzlem karakterlerinde utf8mb4'ün dört bayt kullanırken utf8mb3'ün o karakteri hiç saklayamadığını yazıyor; Türkçe harfler BMP içinde olduğundan fark çoğunlukla emoji gibi karakterlerde çıkar. Aynı belgeye göre MySQL 8.4'te sunucunun varsayılan karakter kümesi utf8mb4, varsayılan collation'ı utf8mb4_0900_ai_ci — ama bu değerler değiştirilebildiğinden "varsayılan" iddiasını kullandığınız sürüme göre yeniden kontrol etmek gerekir.Bağlantı tarafında dört sistem değişkeni var:
character_set_client, character_set_connection, character_set_results, collation_connection. MySQL'in bağlantı karakter kümesi belgesine göre SET NAMES 'utf8mb4' ilk üçünü tek seferde ayarlar. Sütun doğru olsa da bağlantı farklı kodlama varsayarsa metin yine bozulur; sütuna bakıp bağlantıyı unutmak yaygın bir eksiktir.Türkçe İ/ı için aynı belge, collation isimlendirmesinde ayrı bir "tr"/"turkish" dil belirteci bulunduğunu doğruluyor. Dotted/dotless I'nın büyük-küçük harf eşleşmesini tam olarak nasıl etkilediği incelediğim sayfalarda açıkça yazmıyordu; bunu kesin kural saymak yerine kritik bir arama senaryosunda
turkish collation'ı küçük bir test tabloda deneyip gözlemlemeniz daha güvenli.Toplu düzeltmeden önce: yedek, küçük örnek, iki kez dönüştürme riski
Yukarıdaki çözme/geri çevirme denemesi yalnızca gerçekten "UTF-8 baytları latin1 gibi okunmuş" satırlarda işe yarar. Zaten doğru görünen bir satırı aynı script'ten geçirirseniz bir kez daha yanlış kodlarsınız; bu, mojibake'i düzeltmek yerine ikinci bir bozulma katmanı ekler. Toplu script öncesi tablonun yedeğini alın, script'i önce birkaç satırlık küçük bir örnekte çalıştırıp satır satır karşılaştırın. Site taşıma rehberi, dışa/içe aktarmada dump dosyasının kodlamasının da zincire eklenen ayrı bir halka olduğunu hatırlatan iyi bir tamamlayıcı.
Yeni projede baştan doğru kurmak için kısa yerleşim
Yeni bir projede zinciri baştan tek kodlamaya sabitlemek, sonradan onarmaktan kolaydır: dosyaları BOM'suz UTF-8 kaydedin, çıktıda
charset=utf-8 bildirin, veri tabanını utf8mb4 ile kurup her bağlantıda SET NAMES veya sürücünün eşdeğerini çağırın. Aynı ortamı yeniden kurma rehberi, bu ayarların proje genelinde tekrar üretilebilir kalması için iyi bir başlangıç noktası; dil seçimi çerçevesi konusu ise mbstring gibi araçların dile göre nasıl değiştiğine dair genel bir bakış sunuyor.Özetle
"?" bir kayba, kare işareti çözülemeyen bir bayta, "Ç"/"ö" tipi dizi ise genelde geri çevrilebilir bir yanlış okumaya işaret eder. Zinciri dosyadan sütuna tek tek sınamadan hiçbir halkayı suçlamayın; toplu düzeltmeden önce mutlaka yedek alıp küçük bir örnekte deneyin. Sizde bozulma en son zincirin hangi halkasında çıktı: dosyada mı, bağlantıda mı, tabloda mı?
Güncelleme: 21 Ağustos 2026. MySQL 8.4 Reference Manual, WHATWG HTML Living Standard (meta charset) ve Python Unicode HOWTO resmî belgeleri kontrol edildi.
Dijital Dünyanıza Yön Veren Pusula