起因

为了保证数据安全,每天都会对米坛社区的数据库和web文件进行备份,但是就在不久前,因为我们准备把米坛社区的xenforo版本从2.1升级到2.2,所以就开了一个测试机进行模拟升级测试。但是就在测试的时候就突然发现,xf_user这个数据表的大量的行丢失了,行数从5w变为了4.3w。大量用户数据都丢失了。同时检查的备份文件,都有同样的丢失问题。

分析

由于辣鸡宝塔的数据库导入导出都没有返回任何报错信息,所以最开始采用Navicat进行读取,使用Navicat的导出导入,由此得到以下测试结果

宝塔导出 Mysqldump命令导出 Navicat导出
宝塔导入 导入报错、数据丢失 导入报错、数据丢失 正常
Mysqldump命令导入 导入报错、数据丢失 导入报错、数据丢失 正常
Navicat导入 导入报错、数据丢失 导入报错、数据丢失 正常

通过翻阅宝塔源码文件/www/server/panel/class/database.py可得知,宝塔使用的备份指令为/www/server/mysql/bin/mysqldump --default-character-set="+ public.get_database_character(name) +" --force --opt \"" + name + "\" | gzip > " + backupName
由此可以发现与命令备份指令相同,推测应该不是宝塔导出的问题,而且命令本身有问题。通过查阅相关资料,也并没有发现此备份命令有不当之处。

虽然从逻辑分析,应该是导出问题,但是由于导出没有任何报错,所有只能转向导出的报错提示
ERROR 1062 (23000) at line 106: Duplicate entry '???' for key 'username'
ERROR 1062 (23000) at line 109: Duplicate entry '?????' for key 'username'
通过查阅谷歌,发现以下出现同样问题的人
https://www.v2ex.com/t/60317
根据回复,应该是编码问题,但是米坛社区一直使用utf-8编码,从未使用过GBK等中文编码,应该不存在此问题,而且这也无法解释为什么会导致大量数据丢失,因为不可能人人都错误的使用GBK编码作为用户名注册。

由此,只能针对备份出来的SQL文件进行分析,通过对比原数据库和丢失数据了的测试服务器数据库,发现都在命令导出的SQL文件中,丢失的数据都位于同一个INSERT INTO语句,因为语句出错,导致整条INSERT INTO语句无法正常执行,只能跳过,INSERT INTO语句中所有用户数据都没被成功导入。

由于INSERT INTO语句过长,有大概3000条用户信息,所有只能在原数据库一条条查看可疑的可能导致乱码的用户名,最后找到了两个使用emoji的用户名,并且在命令导出的sql文件中,这两个用户的用户名都乱码了,使用Navicat导出的由于软件正确识别,正常的写入了文件,所以一切正常。

归因

通过查阅资料,MySQL如果使用普通的UTF-8编码,是无法储存emoji的,xenforo提供了$config['fullUnicode'] = true;选项来解决此问题,此选项是默认开启的,但是当初在使用宝塔创建数据库的时候,并不知道有这个限制,整个数据库使用了默认的UTF-8编码,而事实上应该使用UTF-8mb4,xenforo又自动的在创建表的使用UTF-8mb4进行了储存,所以emoji被正常储存了。但是宝塔在备份时使用了参数--default-character-set="+ public.get_database_character(name)这导致备份输出使用了UTF-8,所有的emoji数据乱码,数据丢失。

所有这个问题就是在 xenforo没有检测直接使用UTF-8mb4 + 宝塔默认没有使用兼容性更好的UTF-8mb4 + 宝塔导入数据库不返回报错 的多重因素下产生了

解决

解决方法也很简单,直接修改数据库排序规则为utf8mb4_general_ci即可

参考资料:
https://xenforo.com/docs/xf2/unicode/
https://www.v2ex.com/t/60317


信任是利益的武器,捅伤别人,保护自己。