MySQL on duplicate key update返回自增主键问题

一、业务场景

近期项目中遇到了这样的表关联关系:需要先插入表A中的记录a,再根据记录a的主键id,插入表B中的关联数据。
表A具有一定的唯一性约束,所以在插入的时候就很容易会想到使用MySQL的on duplicate key update关键字来进行一个排他性的插入更新操作。
若表A数据在插入时有冲突,则直接自动转为根据唯一索引进行更新的操作。

插入语句很简单,大致如下所示。


    

随后表B中数据填充A返回的id,再进行插入。

二、发现问题

看上去好像没什么问题。但是将逻辑写好进行部署自测时,发现表B中的许多数据无法与表A中的数据关联起来。
检查数据发现,表B中的aid,大量没有入库,并且表A的主键id总是跳跃增加,不是以自然数进行自增。

网上冲浪查找资料才发现,on duplicate key update有一个很坑爹的地方,那就是就算本次操作并没有进行插入,而是一个更新操作,主键id也会进行自增。
由于表A数据的唯一性约束,大多数时间,sql语句根据唯一键所做的都不是插入操作而是一个更新操作,此时Mybatis填充的A.id,就是自增后的id(而不是我所预想的,已存在数据的id)。
而此id其实根本就没有对应数据存在,才会导致表B中产生许多未关联的数据。

三、解决方案

(一)修改内核参数

有一种方式是通过修改MySQL内核参数(innodb_autoinc_lock_mode)来解决。
innodb_autoinc_lock_mode这个参数控制着在向有auto_increment 列的表插入数据时,相关锁的行为。

innodb_autoinc_lock_mode有三个取值:0 (tradition)、1(consecutive)、2(interleaved)
数据库默认是1的情况下,就会发生上面的那种现象。每次使用insert into … on duplicate key update 的时候,数据库只是简单地自增id,不管实际是insert还是update操作。
将该参数改为0后,数据库则只有在实际发生insert的时候才会自增主键,但是每次都会锁表,并发性不太好。

考虑到性能问题,没有使用这种方案。

(二)分布式锁

最终使用的方案是将insert与update操作分离开,并且使用分布式锁代替数据库锁。
先查询库中是否有冲突数据,若有则执行更新语句,若没有再来执行insert操作(通过redis锁保证顺序)。

其实若是修改表结构,在表B中存入表A中的冗余字段(唯一键信息),而不是A的主键,也能解决这个问题。
但是若频繁使用on duplicate key update,表A中自增主键id依然会跳跃增加,造成不必要的浪费。

你可能感兴趣的:(MySQL on duplicate key update返回自增主键问题)