引言
了不起最近处理了一个生产问题,具体情况是这样的:
就是两个sql语句在同一个代码块中,第一个sql语句是对设备状态根据设备ID批量删除,第二个sql语句是批量插入,插入的设备ID和删除的设备ID是同一个。
本地和测试环境当时都好好的,可是上了生产环境,报错了
错误是存在重复的唯一索引,代码逻辑很简单
可以看到就是一个批量删除,再批量插入,测试环境当时没有用多个实例进行测试,上产是有多个实例的,错误的发生情况当天下班后一直分析到晚上12点,终于分析出来了,如下图:
可以看到是同时来了两个请求,分别落到了2台机器,这段代码在2个机器上执行就不能保证顺序性了,这个要加锁的话也是分布式锁。
不过还可以通过数据库的行锁来实现,接下来我们就一起来看看INSERT ... FOR UPDATE语句。
MySQL的 INSERT ... FOR UPDATE
语句可以用于在插入新行的同时对这些行进行加锁,从而防止其他事务对这些行进行修改。下面简要介绍 INSERT ... FOR UPDATE
语句的原理和应用场景,并举例说明其具体用法。
一、INSERT ... FOR UPDATE
语句的原理
INSERT ... FOR UPDATE
语句会在执行插入操作时,对插入的新行进行排他锁(Exclusive Lock)的加锁操作,从而避免其他事务对这些新行进行修改。
这种锁定机制同样是基于MySQL的事务隔离级别实现的,只有在可重复读(REPEATABLE READ)和串行化(SERIALIZABLE)隔离级别下,INSERT ... FOR UPDATE
语句才能生效。
二、INSERT ... FOR UPDATE
语句的应用场景
- 防止重复插入
在某些业务场景下,可能需要对某些数据进行唯一性约束,避免重复插入相同的数据。使用 INSERT ... FOR UPDATE
语句可以避免并发的情况下,多个事务同时插入相同的数据。
- 实现悲观锁
在某些业务场景下,需要对某些数据进行严格的控制,确保数据不会被其他事务修改。使用 INSERT ... FOR UPDATE
语句可以实现悲观锁,确保数据的安全性。
- 实现乐观锁
使用 INSERT ... FOR UPDATE
语句可以实现乐观锁机制,即在插入数据的同时,记录数据的版本号或时间戳等信息,如果在插入时发现版本号或时间戳已经存在,则说明其他事务已经插入了相同的数据,需要进行相应的处理。
三、INSERT ... FOR UPDATE
语句的应用举例
下面举例说明 INSERT ... FOR UPDATE
语句的具体用法。
假设有一个用户表user,其中包含用户ID和用户名两个字段,现在需要往用户表中插入新用户,假设插入流程如下:
- 根据用户ID查询用户是否已经存在
- 如果用户不存在,则插入新用户
在这个过程中,需要对用户ID进行唯一性约束,避免重复插入相同的用户。
可以使用如下的SQL语句来实现插入操作:
START TRANSACTION; SELECT * FROM user WHERE user_id = '123' FOR UPDATE; INSERT INTO user(user_id, username) VALUES ('123', 'John'); COMMIT;
在这个SQL语句中,SELECT ... FOR UPDATE
语句用于对选定的行进行加锁,防止其他事务对这些行进行修改,从而避免重复插入相同的用户。如果查询结果为空,则可以插入。
四、生产问题的解决
了不起这边处理的方式就很简单了,直接将批量插入sql的语句末尾加上了 FOR UPDATE。语法参考:
insert into tablename values() on duplicate key update columnName = valueName;
由于具体涉及公司隐私就不贴sql语句了,原理就是当执行插入的时候,如果已经有这条数据存在,那么就更新它,就不会存在说已经插入了的数据在插入一条会报重复Key这个情况。
简单的INSERT ... FOR UPDATE你掌握了吧,后面我们继续学习。