Mysql行锁引发的问题
数据表结构:
事物隔离级别(可重复读):
业务场景:
需要在内存中处理大量数据,处理完后保存数据库。**
演示代码:
一、对tb_pv表进行条件删除( ip=1);
二、开启一个线程对tb_pv表进行新增包含字段(ip=1),线程join等待线程one执行完后再执行主线程;
三、调用后线程后,线程一直等待;
四、查询一下数据库发现出现两个锁了,都指向tb_pv表;如下
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
五、等待片刻,超时后出现错误;如下
问题分析:
直接模仿代码操作mysql
一、同样开启一个事物,删除tp_pv ip=’1’的记录;(不提交也不回滚)
二、模仿线程操作一tb_pv ip=’1’进行新增,发现插入不了,出现和代码执行后的错误一样;
三、插入|删除一条和ip=’1’不相关的记录,发现也是执行不了;
四、在插入时候查询一下数据库的锁,发现和代码执行的一样,有两个锁,都对应着tb_pv
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
五、把删除操作回滚,发现插入操作ok了
问题:
在插入的时候出现了两个排它锁,并且都是行锁,为什么插入和ip!=’1’也不成功呢,lock_space、lock_page、lock_rec代表锁中了一行为第3页表空间 ID为230。
六、在tb_pv表中加入主键id,使用主键删除,并且调用insert发现是ok的;
以此推论在范围删除时,数据库其实是锁着了表,只有在删除指定唯一数据的时候才是锁行;
解决方法
1、直接把锁住的事物结束;
查询事物
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
关闭
kill trx_mysql_thread_id;
2、将代码拆分,把删除操作单独起一个事物,不存在原来的事物中。
设置@Transactional(propagation = Propagation.REQUIRES_NEW),@Transactional默认是Propagation.REQUIRED,如果父方法调子方法都存在@Transactional注解,则会把子方法的事物默认加入到父方法的事物中,而加入propagation = Propagation.REQUIRES_NEW意思是,创建一个新事务,如果当前存在事务,将这个事务挂起。也就是说如果当前存在事务,那么将当前的事务挂起,并开启一个新事务去执行REQUIRES_NEW标志的方法。这样就能防止去同时获取锁。
3、业务放入线程处理,处理完把数据返回出来,再保存。