开发笔记
MySQL 一些奇怪的坑
字符集、时区、隐式类型转换和 ONLY_FULL_GROUP_BY,四个最常撞上的行为差异
MySQL 的很多「奇怪」其实是宽松策略:能猜就猜,猜错了也不报错。下面四个是最常见的。
utf8 不是真 utf8
utf8 只支持最多 3 字节,存 emoji 和部分生僻字会直接报 Incorrect string value。要真的 UTF-8,用 utf8mb4:
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;连接串也要跟着改:
jdbc:mysql://host:3306/db?useUnicode=true&characterEncoding=utf8mb4只改表不改连接,写入照样被截断。
时区
TIMESTAMP 存的是 UTC,读的时候按会话时区转换;DATETIME 存字面值,不做转换。服务端时区不对,同一行数据在两个客户端能读出相差 8 小时的值。
SHOW VARIABLES LIKE '%time_zone%';
SET time_zone = '+08:00';Java 侧推荐在连接串里钉死:
serverTimezone=Asia/Shanghai隐式类型转换
字段是 varchar,查询写数字,MySQL 会把整列转成数字比较,索引直接失效:
-- phone 是 varchar,下面这句走不了索引
SELECT * FROM users WHERE phone = 13800138000;反过来,字段是 int 而传了字符串,通常还能走索引,但会带来别的意外('1abc' 被转成 1 匹配成功)。规则很简单:类型对齐,别指望 MySQL 帮你猜。
ONLY_FULL_GROUP_BY
MySQL 5.7 起默认开启,SELECT 里出现非聚合、非分组的列会直接报错:
-- 报错:name 不在 GROUP BY 里
SELECT dept_id, name, COUNT(*) FROM emp GROUP BY dept_id;老代码迁移过来最容易撞上。两种改法:把列加进 GROUP BY,或者用 ANY_VALUE(name) 明确表示「随便取一个」。关掉这个模式能跑,但语义就不确定了。
顺手两个
- 表名大小写:Linux 上
lower_case_table_names=0,User和user是两张表;从 Windows 迁过来经常找不到表。 TIMESTAMP上限是 2038-01-19,长期存时间用DATETIME。