MySQL SELECT查询未找到已知存在值 触发唯一键重复报错问题
根因分析
核心原因:InnoDB 快照读与当前读的可见性差异
你使用的 InnoDB 引擎默认采用*可重复读(REPEATABLE READ,RR)*隔离级别:
- 普通 SELECT 查询属于快照读,读取的是事务启动时刻的快照版本数据,未提交的变更对其他事务不可见
- INSERT 操作执行唯一键校验时属于当前读,会读取最新的所有数据(包括未提交的其他事务写入),避免出现唯一键冲突
你遇到的是典型的并发场景问题,执行时序如下:
- 事务 A 第一次调用存储过程,Lookup1 快照读表为空,成功插入数据,此时事务 A 尚未提交
- 事务 B 在事务 A 提交前启动,执行 Lookup1 快照读,读取的是事务 B 启动时的快照,看不到事务 A 未提交的插入数据,因此 pricingHeaderID1 为 NULL
- 事务 B 执行 INSERT 操作,唯一键校验的当前读可以读到事务 A 写入的记录,因此直接抛出唯一键重复错误
其他低概率排查点
可确认两次调用传入的唯一键字段(UserID、ClientName、SubjectProperty、DatePrepared)是否存在隐式差异:你使用的 utf8_general_ci 排序规则会忽略字符串末尾空格、大小写差异,如果两次传参存在上述差异也可能导致查询匹配异常。
解决方案
方案1:修改 Lookup1 为当前读
在查询语句末尾加 FOR SHARE 强制读取最新已提交数据,即可提前检测到已存在的记录:
SELECT `PricingHeaderID` INTO pricingHeaderID1 FROM `PricingHeaders` WHERE `UserID` = UserID_In AND `ClientName` = ClientName_In AND `SubjectProperty` = SubjectProperty_In AND `DatePrepared` = DatePrepared_In FOR SHARE;
方案2:改用 INSERT ON DUPLICATE KEY UPDATE 语法
直接省略前置查询逻辑,用原生语法实现不存在则插入、存在则更新,完全规避并发冲突和自增ID浪费问题,代码更简洁可靠:
INSERT INTO `PricingHeaders` ( `UserID`, `ClientName`, `Subdivision`, `SubjectProperty`, `DatePrepared`, `MonthsSearched`, `Bedrooms`, `Bathrooms`, `Stories`, `SquareFeet`, `YearBuilt`, `HasPool` ) VALUES ( UserID_In, ClientName_In, Subdivision_In, SubjectProperty_In, DatePrepared_In, MonthsSearched_In, Bedrooms_In, Bathrooms_In, Stories_In, SquareFeet_In, YearBuilt_In, HasPool_In ) ON DUPLICATE KEY UPDATE `Subdivision` = Subdivision_In, `MonthsSearched` = MonthsSearched_In, `Bedrooms` = Bedrooms_In, `Bathrooms` = Bathrooms_In, `Stories` = Stories_In, `SquareFeet` = SquareFeet_In, `YearBuilt` = YearBuilt_In, `HasPool` = HasPool_In;
方案3:调整事务隔离级别
如果业务允许,可将事务隔离级别调整为读已提交(READ COMMITTED),快照读每次都会读取最新的已提交数据,也能解决该问题,需提前评估对其他业务逻辑的影响。
内容的提问来源于stack exchange,提问作者jsureke
相关产品推荐
相关产品推荐

