MariaDB主键相似字符唯一性冲突问题及PostgreSQL行为咨询
这个问题我之前在开发Django项目时碰到过好几次,核心就是数据库的**排序规则(Collation)**在搞怪!
MariaDB/MySQL默认用的排序规则(比如utf8_general_ci、utf8mb4_general_ci)是重音不敏感的——这类规则会把带重音的字符(像ó、é)和对应的无重音字符(o、e)当成完全一样的内容。所以当你插入主键值分别为ó和o的两行时,数据库会判定主键重复,直接抛出1062错误。
而SQLite没这个问题,是因为它默认对字符串用二进制比较,直接看字符的二进制编码是否相同,ó和o的编码不一样,自然不会被视为重复。
要让数据库区分这些带重音的字符,你需要把表或者主键列的排序规则改成重音敏感的,这里有两个靠谱的方案:
方案1:二进制排序规则(简单直接)
二进制排序规则会严格按字符的二进制编码来比对,完全区分所有不同字符(包括重音、大小写)。执行下面的SQL就能修改整个表的设置:
-- 先把字符集改成utf8mb4(推荐,支持所有Unicode字符),再设置二进制排序规则 ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
如果你只想改主键列的规则,单独修改即可:
ALTER TABLE table_name MODIFY term VARCHAR(64) NOT NULL PRIMARY KEY COLLATE utf8mb4_bin;
方案2:Unicode标准排序规则(排序更合理)
如果你希望字符排序符合Unicode标准,同时区分重音,可以用utf8mb4_0900_as_cs(适用于MySQL 8.0+、MariaDB 10.5+)。这个规则不仅区分重音,还区分大小写,而且排序逻辑比二进制更人性化(不会单纯按编码值排序):
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs;
小提醒:修改排序规则前最好备份一下数据,避免意外;新项目建议一开始就配置好正确的字符集和排序规则,省得后期折腾。
默认情况下,PostgreSQL不会出现这个问题。
PostgreSQL的字符串比较默认依赖数据库的collation,大多数默认的collation(比如en_US.utf8)都是区分重音的,'ó'和'o'会被判定为不同的值。只有当你特意用了忽略重音的collation(比如en_US.utf8@accent=ignore),或者手动用unaccent()函数处理字符串后再比较,才会把它们当成一样的。
内容的提问来源于stack exchange,提问作者beruic

