في MySQL ترميزٌ اسمه utf8 وليس UTF-8. فهو يخزّن ثلاثة بايتات على الأكثر لكل محرف، وكلُّ إيموجي وبعضُ المحارف النادرة تحتاج أربعة. والنصُّ الذي يحوي واحدًا منها إمّا يُرفَض وإمّا يُبتَر بصمت عند تلك النقطة — ولهذا قد يختفي تعليقٌ من فقرةٍ فما بعدها.

استخدم utf8mb4 في كل مكان

ALTER DATABASE shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE comments CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

وفي الاتّصال أيضًا، وهو النصف الذي يفوت الناس

// PDO
new PDO('mysql:host=localhost;dbname=shop;charset=utf8mb4', $u, $p);
; my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
جدولُ utf8mb4 يُقرَأ عبر اتّصال latin1 فيعيد نصًّا مشوَّهًا ويكتب نصًّا مشوَّهًا. الجدول صحيح والاتّصال ليس كذلك، والبيانات تفسد في الطريق. فاضبط الاثنين.

أيُّ ترتيب

  • utf8mb4_unicode_ci — يرتّب ويقارن بقواعد يونيكود عبر اللغات. وهو الافتراضُ الآمن.
  • utf8mb4_0900_ai_ci — افتراض MySQL 8، ويونيكود أحدث، وأسرع قليلًا.
  • utf8mb4_bin — بايتات بحرفها. استخدمه للرموز والبصمات، ولا للأسماء أبدًا.

الترتيب هو الذي يقرّر المقارنة. فتحت ترتيبٍ لا يميّز حالة الأحرف يطابق WHERE name = 'ali' الاسمَ Ali — وذلك ما تريده عادةً في بحث، ولا تريده في مفتاح واجهةٍ برمجيّة.

اعثر على ما لا يزال خاطئًا

SELECT table_name, table_collation
  FROM information_schema.tables
 WHERE table_schema = 'shop' AND table_collation NOT LIKE 'utf8mb4%';

SELECT table_name, column_name, character_set_name
  FROM information_schema.columns
 WHERE table_schema = 'shop' AND character_set_name IS NOT NULL
   AND character_set_name <> 'utf8mb4';

الأعمدة تُحوَّل على حدة عن الجداول. فجدولٌ مُعلَنٌ utf8mb4 قد يحمل مع ذلك أعمدة latin1 من سنواتٍ خلت، وتلك هي التي تنكسر.

انسخ احتياطيًّا قبل التحويل. فهو يعيد كتابة كل صفّ، وقد يطول على جدولٍ كبير، وحدودُ حجم الفهارس تعضّ أحيانًا في الإصدارات الأقدم.