捕获1062错误码最可靠,但需设PDO::ATTR_ERRMODE为PDO::ERRMODE_EXCEPTION并禁用模拟预处理;须双校验$errorInfo[1]===1062和getCode()==='23000';禁用INSERT IGNORE以免掩盖真实问题;事务中冲突须手动回滚。

直接捕获 1062 错误码是最可靠的方式,但必须配合正确的 PDO 配置和错误类型判断;盲目用 INSERT IGNORE 或只匹配错误字符串,大概率掩盖字段截断、权限不足等真实问题。
PHP 中必须设置 PDO::ATTR_ERRMODE 为 PDO::ERRMODE_EXCEPTION
默认情况下 PDO 不会抛出异常,execute() 失败只返回 false,导致 try-catch 完全失效。不设这个属性,后续所有错误处理逻辑都形同虚设。
- 必须在创建
PDO实例后立即调用setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) - 别漏掉
PDO::ATTR_EMULATE_PREPARES => false,否则预处理语句可能被客户端模拟,导致错误码丢失或类型转换异常 - 不要在事务外临时修改该属性——不同连接实例间不共享设置
捕获 1062 要同时检查 SQLSTATE 和错误码
仅靠 $e->getCode() === '23000' 或 $e->errorInfo[1] === 1062 都不够稳妥:前者在部分驱动下可能返回空字符串,后者在 MySQL 8.0+ 某些配置中可能被覆盖。应双条件校验。
- 优先用
$e->errorInfo[1] === 1062——这是 MySQL 原生错误码,最稳定 - 兜底加
$e->getCode() === '23000',覆盖 PostgreSQL 或其他兼容驱动场景 - 绝对不要用
strpos($e->getMessage(), 'Duplicate entry')——提示文本随版本、语言包、MySQL 配置(如lc_messages)变化,不可靠
INSERT IGNORE 的静默行为常被误用
INSERT IGNORE 不是“只忽略唯一冲突”,它会吞掉所有警告级错误,包括 1265(数据截断)、1366(字符集转换失败)、甚至权限不足的 1142。业务上看似成功,实际数据已损坏。
立即学习“PHP免费学习笔记(深入)”;
- 仅适用于明确接受“丢数据也无妨”的场景,比如埋点日志去重写入
- 若需确认是否真因唯一冲突被忽略,必须结合
rowCount()返回值:0 表示被忽略,但无法区分是冲突还是其他原因 - 高并发下,
INSERT IGNORE可能引发间隙锁竞争,尤其在二级唯一索引上,比ON DUPLICATE KEY UPDATE更易阻塞
事务中唯一冲突必须手动回滚
ThinkPHP、Laravel 等框架的模型方法(如 save()、create())抛出异常时,不会自动回滚外层事务。PDO 抛出 PDOException 后,事务仍处于活跃状态,后续语句可能继续执行,造成部分写入。
- 必须用
catch (\Throwable $e)(不能只写Exception),因为某些底层错误是Error子类 - 捕获后第一件事是调用
$pdo->rollback()或框架对应的回滚方法(如Db::rollback()) - 重试时需新建模型实例或重置状态——复用旧对象可能因内部标记走错 SQL 分支(比如误触发
UPDATE)
真正难处理的不是怎么捕获 1062,而是当它和 ON DUPLICATE KEY UPDATE 的竞态、findOrCreate 的隔离级别限制、或批量插入时混合错误混在一起时,如何分清哪条记录因冲突失败、哪条因网络中断失败——这时候单靠错误码已经不够,得靠幂等键 + 应用层状态追踪。



















