修复MySQL中泰语字符显示为?的问题 — UTF-8字符集指南

您将泰语文本插入MySQL,但查询回来时看到???或乱码字符,如àž àž²àžŠàž²àžšàž±àžš。这是数据库、表、PHP连接和客户端之间的字符集不匹配。这是默认配置的共享主机环境中最常见的问题之一。

本指南解释了根本原因,并提供逐步修复方案——从phpMyAdmin到PHP连接——无需访问服务器级MySQL配置。

快速修复:将字符集设置为utf8mb4,排序规则设置为utf8mb4_unicode_ci,在数据库级别、表级别和PHP连接中都要设置。

1. 为什么泰语字符显示为?

MySQL使用字符集(字符编码)和排序规则(排序规则)将数据存储为二进制字节。当以下情况发生时会出现问题:

当MySQL无法将传入字节映射到表字符集支持的字符时,它会用?替换它们或存储损坏的数据。

2. Latin1 vs UTF-8 vs utf8mb4

字符集泰语支持表情符号推荐
latin1否否避免
utf8 (MySQL)是否(最多3字节)可用但不完整
utf8mb4是是(4字节)推荐

重要:MySQL的utf8并不是标准UTF-8。它每个字符最多只支持3个字节,意味着表情符号和某些字符会被静默损坏。始终使用utf8mb4。

3. 通过phpMyAdmin修复 — ALTER TABLE

通过DirectAdmin打开phpMyAdmin,按照以下步骤操作:

步骤1:更改数据库字符集

点击您的数据库,进入操作标签,找到"排序规则",选择utf8mb4_unicode_ci,然后点击执行。或者直接运行SQL:

ALTER DATABASE `your_database_name`
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

步骤2:转换所有表

对每个表运行此命令(相应替换表名):

ALTER TABLE `your_table_name`
  CONVERT TO CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

CONVERT TO关键字更改表中每一列的字符集。这与DEFAULT CHARACTER SET不同,后者只更改新列的默认值。

⚠️ 始终先备份:在运行ALTER TABLE之前,将数据库导出为.sql文件。如果转换过程中数据损坏,您将需要备份来恢复。

4. 修复PHP连接

即使数据库使用utf8mb4,MySQL也不知道您的PHP脚本发送的是什么字符集,除非您明确告知它。

选项1:PDO(推荐)

// PDO connection with charset
$dsn = "mysql:host=localhost;dbname=mydb;charset=utf8mb4";
$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
]);

选项2:mysqli

// mysqli connection
$conn = new mysqli($host, $user, $pass, $dbname);
$conn->set_charset("utf8mb4");

5. WordPress的修复方法

WordPress自4.2版本起自动处理此问题。验证您的wp-config.php包含:

define('DB_CHARSET', 'utf8mb4');
define('DB_COLLATE', 'utf8mb4_unicode_ci');

注意:如果您迁移了旧的WordPress网站,表可能仍然使用utf8。对每个WordPress表运行ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4,或者如果您的主机支持,使用WP-CLI命令wp db convert-charset。

6. 为什么泰语文本变成??? — 每一层的字符集/排序规则不匹配

理解这个问题最重要的一点是,'泰语文本变成???'从来不是由单一故障点引起的。它是由数据经过的多个层之间的字符集不匹配引起的——从用户输入的HTML表单,一直到磁盘上写入的实际字节。如果任何一层错误地解释了字节,您的泰语字符就会立即损坏,您看到的症状取决于哪一层出错。

想象您的泰语数据按顺序经过四个层。每一层都必须同意使用utf8mb4:

层设置位置如果错误会发生什么
1. HTML / 表单<meta charset="UTF-8">和Content-Type标头浏览器从一开始就以错误的编码发送字节
2. PHP连接DSN中的SET NAMES utf8mb4 / charset=utf8mb4MySQL认为客户端使用latin1并错误地转换字节
3. 表 / 列CREATE/ALTER时的CHARACTER SET utf8mb4存储单元只接受latin1,所以超出范围的字节变成?
4. 数据库ALTER DATABASE ... CHARACTER SET utf8mb4新表将永远继承错误的字符集

常见症状以及如何判断是哪一层出问题:

黄金法则:验证所有四层都是utf8mb4。只修复表但忘记连接(或反之)会让问题继续存在,因为数据必须通过每一层而没有任何不匹配。

7. 在每一层正确设置utf8mb4(数据库·表·连接·HTML)

本节收集了您需要在所有层运行的每个命令,按照您实际诊断问题的方式排序。首先检查当前状态,找出哪一层仍然是latin1:

步骤1:检查每一层的当前字符集

在DirectAdmin中打开phpMyAdmin,进入SQL标签,运行这些检查:

-- Database charset/collation
SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME = 'your_database_name';

-- Charset of every table in the database
SELECT TABLE_NAME, TABLE_COLLATION
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_database_name';

-- Charset of each column in a table
SHOW FULL COLUMNS FROM `your_table_name`;

-- Charset the connection is currently using
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';

如果character_set_client、character_set_connection或character_set_results显示latin1,则连接层有问题,必须在您的PHP代码中修复。

步骤2:用SQL在每一层设置字符集

-- Database layer
ALTER DATABASE `your_database_name`
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

-- Table layer (CONVERT TO changes every column at once)
ALTER TABLE `your_table_name`
  CONVERT TO CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

-- Connection layer (run after every connect)
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;

对于HTML层,确保显示和提交数据的页面在<head>中声明正确的meta字符集:

<meta charset="UTF-8">
// and the PHP side sends a matching header
header('Content-Type: text/html; charset=utf-8');

⚠️ 顺序很重要:在插入新数据之前将连接设置为utf8mb4。如果在连接仍然是latin1时插入,存储的数据从一开始就损坏了——即使表已经是utf8mb4。

8. 将latin1表/列转换为utf8mb4(ALTER、CONVERT)

当您有用latin1创建的遗留表时,转换需要格外小心,因为有两种不同的情况:(1)列数据已经是正确的泰语只是标签错误,或者(2)数据已经乱码,因为以错误的编码存储。

普通情况:使用CONVERT TO转换整个表

如果现有数据显示正确(通过字符集与插入时使用的字符集匹配的连接),使用CONVERT TO,MySQL会正确地将每列的字节重新编码为utf8mb4:

ALTER TABLE `posts`
  CONVERT TO CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

一次性转换所有表(生成语句)

如果您有数十个表,运行此查询生成每个表的ALTER语句,然后复制输出并运行它:

SELECT CONCAT(
  'ALTER TABLE `', TABLE_NAME,
  '` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'
) AS sql_command
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_database_name'
  AND TABLE_TYPE = 'BASE TABLE';

特殊情况:通过BLOB逐列转换(防止数据丢失)

如果CONVERT TO损坏了您的数据(因为现有数据已经是仅被标记为latin1的utf8mb4字节),请使用BLOB循环转换,它告诉MySQL"不要触碰字节,只是重新标记字符集":

// Step 1: change the column to BLOB (keep raw bytes)
ALTER TABLE `posts`
  MODIFY `content` BLOB;

// Step 2: change back to TEXT with the utf8mb4 label
ALTER TABLE `posts`
  MODIFY `content` TEXT
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

使用哪种方法:如果现有数据在phpMyAdmin中显示正确(默认使用utf8mb4),使用普通CONVERT TO。但如果数据只有当连接设置为latin1时才显示正确,使用BLOB技术来保留原始字节。

9. 修复已乱码的数据(双重编码)+ WordPress wp-config

双重编码是指utf8mb4数据被解释为latin1然后再次编码为utf8,将单个泰语字符变成一长串乱码字符,如ภาษาไทย而不是'ภาษาไทย'。在这种情况下数据没有丢失,可以恢复。

如何恢复双重编码数据

技巧是告诉MySQL将当前字节解释为latin1(剥去外层编码),然后以utf8mb4读回:

// Preview the result first (does not modify real data)
SELECT CONVERT(CAST(CONVERT(`content` USING latin1) AS BINARY) USING utf8mb4)
FROM `posts` LIMIT 5;

// Once the preview looks right, run the real UPDATE (always back up first!)
UPDATE `posts`
SET `content` = CONVERT(CAST(CONVERT(`content` USING latin1) AS BINARY) USING utf8mb4);

⚠️ 永远不要运行UPDATE两次:双重编码恢复命令必须恰好运行一次。再次运行会将数据损坏到另一层。先导出备份,然后用SELECT测试,直到确认无误后再运行UPDATE。

在WordPress中设置字符集(wp-config.php)

WordPress自4.2版本起为您设置字符集,但如果您迁移了旧网站,验证wp-config.php包含:

<?php
// WordPress database charset
define('DB_CHARSET', 'utf8mb4');
define('DB_COLLATE', 'utf8mb4_unicode_ci');

如果旧WordPress表仍然是utf8或latin1,对每个WordPress表(如wp_posts、wp_postmeta、wp_comments)运行ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4,或者如果您的主机支持,使用WP-CLI:

# Convert the charset of every table via WP-CLI
wp db query "ALTER DATABASE \`$(wp config get DB_NAME)\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

DirectAdmin上的WordPress:如果您没有SSH/WP-CLI访问权限,可以通过phpMyAdmin使用第8节中的生成器查询来转换每个表,然后复制并运行输出——结果是相同的。

10. 修复清单

  1. 在进行任何更改之前备份数据库。
  2. 在phpMyAdmin中将数据库字符集设置为utf8mb4。
  3. 对每个表运行ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4。
  4. 在PDO DSN字符串中添加charset=utf8mb4。
  5. 在每次连接后执行SET NAMES utf8mb4。
  6. 通过插入泰语文本并选择回来进行测试。
  7. 如果问题仍然存在,使用SHOW FULL COLUMNS FROM table_name检查列。

常见问题(FAQ)

为什么泰语数据在phpMyAdmin中看起来正常,但在我的网站上显示为????

因为phpMyAdmin默认将其连接设置为utf8mb4,所以它能正确读取数据。但您的网站PHP代码在连接后可能没有执行SET NAMES utf8mb4,所以MySQL以latin1格式返回数据,导致显示错误。解决方法是在PDO DSN中添加charset=utf8mb4,或每次使用mysqli时调用$conn->set_charset("utf8mb4")。

转换为utf8mb4后,我的旧???数据会恢复吗?

不会。如果数据已经以?存储,原始字节在插入时就已丢失(latin1列无法接受utf8字节)。那些数据永久丢失,只能从备份中恢复。但是,如果症状是mojibake(ภาษา),字节是完整的,可以通过双重编码技术恢复。

我应该使用utf8mb4_unicode_ci还是utf8mb4_general_ci?

使用utf8mb4_unicode_ci。它按照Unicode标准更准确地对多语言字符进行排序和比较,而utf8mb4_general_ci稍快但不够准确。对于混合泰语和其他语言的网站,unicode_ci是更好的选择(MySQL 8还提供了更新的utf8mb4_0900_ai_ci)。

转换字符集之前我真的需要备份吗?

是的——特别是对于CONVERT TO和双重编码UPDATE,它们会修改真实数据。首先通过DirectAdmin中的phpMyAdmin将数据库导出为.sql文件。如果结果不符合预期,您可以进行恢复。

支持完整MySQL和phpMyAdmin的主机服务

AsiaGB主机从500泰铢/年起,配备DirectAdmin、phpMyAdmin、PHP 7.4/8.2/8.3和SSD存储——运行专业网站所需的一切。

查看主机方案