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;。另外,执行 INSERTUPDATEDELETE 语句时,InnoDB会自动为涉及的行加上排他锁。

 之所以update某条数据后不提交(加了排他锁,其他事务不能读),同时select还能查出这条数据,是因为select查的是快照。如果用SELECT ... FOR UPDATESELECT ... 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 调用或循环处理大量数据! 这会让事务一直开着,锁一直不释放,高并发下直接把数据库拖死。

posted @ 2026-08-12 14:35  杨吃羊  阅读(6)  评论(0)    收藏  举报