mysql锁,spring,mybatis框架下mysql锁
在工具窗口(DBeaver/Navicat)中验证行锁
场景一:
窗口1(session1)执行
BEGIN;
update table set name ='test' where id = 1;
id是主键,有索引,不执行commit会一直锁住这一行,此时在窗口2(session2)执行
update table set name ='test2' where id = 1;
窗口2会卡住,提示一直在执行中,此时窗口3(session3)执行 update table set name ='test2' where id = 2;会执行成功,因为这条没被锁,这时再执行窗口1commit,窗口1,2的会瞬间完成。
说明用索引更新只会锁住索引所在行,不会锁其他行。
如果窗口1执行 begin,update后不提交事务,这是窗口2用主键查这条数据可以立刻查到,但数据是更新前的旧数据,如果加 for update会阻塞住,直到窗口1提交事务才能查到,查到的是新数据。
场景二:
窗口1(session1)执行
BEGIN;
update table set name ='test' where age=25;
窗口1不执行commit时,由于age字段无索引,mysql会根据主键逐行判断age的值,如果当前行符合where条件就锁住这行,不符合就不锁,直到把表所有行都扫一遍,这就是所谓的表锁,
表锁其实不存在,只是这么叫,容易产生误解,并不是锁住全表数据。扫描期间其他会话update被锁住的行会被阻塞,如果update不是age=25的数据大概率不会被阻塞(只有刚好卡在session1扫描这行数据还没释放期间会卡住)。
场景三:共享锁(S锁)与排他锁(X锁)的互斥
-
共享锁(Shared Lock,S锁):读锁。允许多个事务同时读取同一行数据,但在所有共享锁被释放前,阻止任何事务对该行进行修改。
-
加锁方式:
SELECT ... FOR SHARE; 或 SELECT ... LOCK IN SHARE MODE;
-
-
排他锁(Exclusive Lock,X锁):写锁。只允许一个事务对某行数据进行读取和修改,其他事务在锁释放前既不能读也不能改该行。
- 加锁方式:
SELECT ... FOR UPDATE;。另外,执行INSERT、UPDATE、DELETE语句时,InnoDB会自动为涉及的行加上排他锁。
之所以update某条数据后不提交(加了排他锁,其他事务不能读),同时select还能查出这条数据,是因为select查的是快照。如果用SELECT ... FOR UPDATE或SELECT ... FOR SHARE就可能被阻塞了。
写sql时很少加共享锁/排他锁,因为用乐观锁(版本号)/redis锁代替了共享锁/排他锁。
原生 MyBatis(非 Spring 环境,纯 JDBC):不是自动提交
SqlSession session = sqlSessionFactory.openSession(); // 注意:默认是 false! session.update("updateUser", user); // 此时数据并没有真正提交!必须手动 commit,否则数据库无变化,锁一直持有 session.commit();
Spring+Mybatis框架下代码中锁的情况
不加 @Transactional(等同于自动提交)
// Service 层方法,不加 @Transactional public void updateWithoutTx() { userMapper.updateNameById(1, "XXX");
// 方法执行完,数据库连接自动关闭,事务自动提交,锁瞬间释放。
// 此时锁已经释放了,其他线程完全感觉不到 }
加 @Transactional(长事务导致锁剧增)—— 生产环境大忌
@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional // 开启事务! public void updateWithLongTx() { // 1. 先执行更新,锁住 id=1 userMapper.updateNameById(1, "XXX"); // 2. 模拟耗时操作(比如调用外部HTTP接口,或者Thread.sleep) try { Thread.sleep(30000); // 假设这里卡了30秒,或者调RPC超时了 } catch (InterruptedException e) { e.printStackTrace(); } // 3. 方法结束,Spring 才会提交事务,释放锁 } }
-
不是自动提交。事务由 Spring 管理,会在整个方法执行完毕后统一提交。
-
如果方法内部执行了
update,然后接着去调用外部 API 或进行复杂计算(耗时较长),锁会一直不释放,直到整个方法结束才释放。这是生产环境锁表/锁行最常见的罪魁祸首。所以加了@Transactional的方法,里面千万不要写耗时的外部 RPC 调用或循环处理大量数据! 这会让事务一直开着,锁一直不释放,高并发下直接把数据库拖死。
浙公网安备 33010602011771号