mysql默认repeatable read不防幻读,分页查+插入时“多出记录”常被误判为程序bug;高并发场景应选serializable或select...for update显式加锁。

事务隔离级别选错导致幻读却以为是程序 bug
MySQL 默认 REPEATABLE READ 能防不可重复读,但不防幻读;PostgreSQL 默认 READ COMMITTED 连不可重复读都不防。很多人在分页查 + 插入场景下看到“多出一条记录”,第一反应是代码逻辑漏判,其实是隔离级别没兜住。
实操建议:
- 高并发写+读的业务(如订单创建后立即查列表),优先设为
SERIALIZABLE或用SELECT ... FOR UPDATE显式加锁,别依赖默认值 - 确认数据库实际生效级别:执行
SELECT @@transaction_isolation,别只看配置文件里写的 -
READ UNCOMMITTED在 MySQL 中基本等同于没开事务,innodb_locks_unsafe_for_binlog=ON会进一步放大风险,生产环境禁用
外键约束被静默忽略的三种情况
建表时写了 FOREIGN KEY,但 INSERT 依然能插进脏数据——不是语法错,是引擎或配置拦不住。
常见错误现象:
- 用
MyISAM引擎建表,外键声明直接被 MySQL 忽略,SHOW CREATE TABLE里都看不到 - InnoDB 表但
FOREIGN_KEY_CHECKS=0,批量导入时关了检查,之后忘了开,后续插入全失效 - 引用字段类型不一致:比如主表
id INT UNSIGNED,子表外键却是INT SIGNED,约束不生效且无报错
检查方法:执行 SELECT COUNT(*) FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = 'your_db' AND CONSTRAINT_NAME LIKE 'fk%',结果为 0 就说明外键根本没建成功。
UPDATE 没加 WHERE 条件却没报错,靠什么第一时间发现?
MySQL 默认允许 UPDATE t SET a=1 这种无条件更新,影响行数可能上万。靠人工肉眼 review SQL 不现实,得靠机制卡住。
实操建议:
- 开发环境 MySQL 启动参数加
--sql_safe_updates=1,强制要求UPDATE/DELETE必须带WHERE或LIMIT - 应用层 ORM 执行前做简单 AST 分析:检测
WHERE子句是否存在、是否为恒真(如1=1)、是否只含主键等安全条件 - DBA 定期跑监控脚本查
information_schema.PROCESSLIST,过滤出Command='Query'且Info包含UPDATE.*SET但不含WHERE的活跃连接
Binlog 格式选 STATEMENT 导致从库数据不一致
STATEMENT 格式记录的是原始 SQL,遇到 NOW()、UUID()、INSERT ... SELECT 等非确定性函数或语句,主从执行结果天然不同。
使用场景判断:
- 纯数值计算、固定条件
UPDATE、主键明确的DELETE——STATEMENT安全且日志小 - 只要 SQL 里出现
RAND()、触发器、存储过程、任何时间函数 —— 必须切ROW格式 -
MIXED不是保险箱:MySQL 自己判断哪些用ROW哪些用STATEMENT,判断逻辑复杂,线上建议直接锁定ROW
验证方式:在从库执行 SHOW SLAVE STATUS\G,重点看 Seconds_Behind_Master 是否持续增长,以及 Relay_Log_Space 是否异常膨胀——这是 STATEMENT 下重放失败的典型信号。
真正难缠的是那些跨事务的隐式依赖:比如两个事务先后更新同一行,STATEMENT 日志顺序对,但执行时机错,从库就可能丢掉中间态。这种问题不会报错,只会让数据慢慢漂移。










