MySQL中的行锁

本文详细介绍了数据库中的行锁类型,包括记录锁、间隙锁和next-key锁,以及它们在唯一索引和普通索引下的加锁规则。在等值查询和范围查询中,不同类型的锁有不同的行为。此外,还探讨了如何通过调整事务中的加锁顺序和批量操作来优化锁的使用,以减少冲突和提高并发性能。举例说明了在购票场景和数据删除操作中如何应用这些策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、行锁分类
  • 1、记录锁:即锁住记录本身

  • 2、间隙锁:锁住一段没有记录的间隙,可以是两条记录的中间部分,也可以是第一条记录的前置部分或最后一条记录的后续部分

    • 2.1 需要注意的是,间隙锁仅仅是阻塞对该间隙进行插入操作,而不阻塞对该间隙的查询操作;如有表tab,有索引idx,对(5,10)加入间隙锁,执行下面两条命令:
    • insert into tab (idx) values(7); 阻塞
    • select * from tab where idx = 7 for update; 不阻塞但无数据
  • 3、next-key锁:是记录锁与间隙锁的结合,特点为左开右闭,如(4,10],由间隙锁(4,10)和记录锁idx = 10组成

2、唯一索引的加锁规则
  1. 等值查询:
  • 若查询记录存在则将next-key锁退化成记录锁,只锁住查询的记录;
  • 若查询记录不存在,则next-key锁退化为间隙锁,锁住记录所在空隙,此处空隙是指与所查询记录(如果存在的话)相邻的两条记录之间的空隙,这两条记录除了可以是数据库表中实际存在的,也可以是其他事务拟插入的(执行了插入sql但是未提交或回滚事务,因为其他事务的插入操作加了记录锁,故而将间隙分为两半)
  • 第二种情况中,若其他事务拟插入的数据进行了提交,则间隙锁固定,若选择回滚,则间隙扩大至下一条记录之前
  1. 范围查询:有以下两种情况,以下的范围内指截至是最后一条符合条件的记录后的间隙
  • >和<:若起始索引记录不存在,则锁住起始索引所在间隙及后续记录和间隙;若存在,则从起始索引记录(不包含)开始,锁住范围内的记录和间隙

  • >=:若起始索引不存在,则锁住起始索引记录所在间隙以及后续记录和间隙,若存在,则从起始索引记录(包含)开始,锁住范围内所有的记录和间隙

  • <=:若起始索引不存在,则锁住起始索引所在间隙与后续的记录和间隙,若存在,则从起始索引记录开始,锁住范围内记录和间隙,并锁住起始索引另一侧的间隙,验证如下:

      如下表:表名为foo,uid为主键索引,也即唯一索引,idx为普通索引
      +-----+-----+------+
      | uid | age | idx  |
      +-----+-----+------+
      |   1 |   1 |    1 |
      |   3 |   3 |    3 |
      |   4 |   4 |    4 |
      |  10 |  10 |   10 |
      |  16 |  16 |   16 |
      +-----+-----+------+
      
      事务A:执行select * from foo where uid >= 10 and uid <= 16 for update;
      事务B:执行insert into foo values(9,9,9);
      事务C:执行update foo set age = 5 where uid = 4;
      事务D:执行insert into foo values(17,17,17);
      其中事务B和C成功执行,事务D阻塞,分析可得[16, 正无穷)被锁住,而[4,10)未被锁住
      
      若是事务A修改为:select * from foo where uid >= 10 and uid < 16 for update;
      则事务D不会被阻塞,也即(16,正无穷)未被锁住
    
3、普通索引的加锁规则
  • 等值查询:若查询记录存在,除了加上next-key锁(即锁住本记录和上一条记录之间的间隙)以外,还会对本记录到下一条记录之间的间隙加锁
  • 范围查询:与唯一索引的区别在于:
    1、>=会将另一侧的间隙也一起锁住;
    2、<会将锁住第一条不满足条件的记录
    如事务A执行:select * from foo where idx>= 10 and idx < 12 for update;
    其中idx为普通索引,按照上表,(4,10)之间会加上间隙锁,且16会被锁住,也即[10,16]锁住
4、行锁的加锁技巧

事务加锁时,其他事务进行写操作时会受到阻塞,这个阻塞时间当然是越短越好,那么对于一个事务当中不同需要加锁的语句,可以采用以下方式控制:

  • 对语句所需要锁住的记录条数进行预估,在不影响业务的情况下,将锁住记录多的语句排在锁住记录少的后面,锁粒度大的容易发生冲突,这样安排可以减少与其他事务冲突时间
  • 将热点记录的加锁操作排在事务后面执行
  • 批量操作分几次进行,减少锁冲突的概率和时间

举个两个简单例子,来自MySQL实战45讲
1、用户A、B要到影院C购票,需要进行以下操作:

  • 用户金额扣除,票数+1
  • 影院金额增加,票数-1

分析业务,容易得出影院的记录是热点记录,相较用户记录更改频繁,因此可以将影院操作语句放在事务后面,减少影院记录上锁时间

2、删除一个表中的前10000条数据,给出三种方案,那种最合适

  • delete from table limit 10000;
  • 开启20个连接,执行delete from table limit 500;
  • 同一连接连续开启20次事务,执行delete from table limit 500;

答案是第三种,原因如下

  • 第一个需要锁住一次性锁住10000记录,范围大,容易发生冲突,且删除期间这10000条记录会被锁住,其他事务被阻塞概率大;
  • 第二个多个连接之间可能发生冲突,互相阻塞,浪费资源;
  • 第三个只有一个连接,每次加锁的记录少,不容易发生冲突;
<think>嗯,用户想知道MySQL默认的机制是怎么工作的。首先,我得回忆一下InnoDB存储引擎的特点,因为MySQL默认的引擎就是InnoDB,而是InnoDB的重要特性。记得InnoDB支持和表级,但默认是对吧?那的类型应该包括共享和排他,也就是读和写。需要确认一下这两种的作用,共享允许其他事务读但不能写,排他则不允许其他事务加任何。 然后用户可能想知道是如何实现的。InnoDB使用索引来实现,所以如果没有用到索引,可能会导致升级为表。比如用户执一个UPDATE语句,如果WHERE条件里的字段没有索引,InnoDB可能就会整个表,这时候性能会受影响。这点需要重点说明,因为实际应用中可能会遇到这种情况,导致并发问题。 还有,用户可能对的机制不太清楚,比如什么时候加,怎么释放。事务中的是在语句执时获取,事务提交或回滚后释放。需要提到定读的情况,比如SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE,这两种语句会主动加。可能还要解释一下不同隔离级别下的为,比如可重复读(RR)和读已提交(RC)的区别,比如间隙的存在。 另外,参考资料里提到InnoDB的是基于索引的,如果查询没走索引就会升级为表。这里需要强调索引的重要性,以及为什么有时候即使有索引,MySQL优化器可能还是会选择全表扫描,从而导致表。比如当数据量小的时候,或者统计信息显示全表扫描更快时,可能不使用索引,这时候的机制也会变化。 可能用户还会关心如何查看的状态,比如使用SHOW OPEN TABLES WHERE In_use > 0这样的命令,或者通过INFORMATION_SCHEMA库中的表来查询。不过用户的问题主要是机制,这部分可能属于进阶内容,可以简要提及。 需要组织一下回答的结构,先说明InnoDB默认使用,然后介绍两种类型,接着讲实现原理基于索引,以及升级的情况。再提到事务中的获取和释放,最后给出优化建议和注意事项。同时,根据用户提供的引用资料,要正确引用相关来源,比如引用[3]中提到的索引和升级的情况。 可能还需要举例子,比如更新不同条件的语句,有的使用索引,有的没有,导致的范围不同。例如,更新主键ID的只会那一,而更新非索引字段会导致表。这样可以更直观地帮助用户理解。 最后,用户可能需要知道如何避免不必要的表,比如确保查询使用索引,分析执计划,避免大事务等。这些优化建议也是回答的一部分,帮助用户实际应用中的问题。</think>### MySQL默认机制工作原理分步解析 MySQL默认存储引擎InnoDB的机制是其实现高并发事务的核心功能,具体工作原理如下: 1. **类型** - **共享(S)**:允许事务读取一数据时阻止其他事务修改该,可通过`SELECT ... LOCK IN SHARE MODE`显式获取[^3] - **排他(X)**:允许事务更新/删除数据时阻止其他事务读写该,通过`UPDATE`/`DELETE`或`SELECT ... FOR UPDATE`自动获取[^1] 2. **索引依赖机制** - 通过索引实现,索引字段查询时仅定符合条件的索引项对应的数据 - 示例:`UPDATE users SET age=20 WHERE id=5`(id为主键)仅定id=5的 - 无索引查询时自动升级为表:`UPDATE users SET status=1 WHERE name='Alice'`(name无索引)触发全表定 3. **冲突检测** ```sql -- 事务A BEGIN; SELECT * FROM accounts WHERE id=1 FOR UPDATE; -- 获取X -- 事务B(立即尝试) UPDATE accounts SET balance=100 WHERE id=1; -- 进入等待状态 ``` 此时事务B会等待直到事务A提交或超时(默认50秒) 4. **粒度转换** - 通过`EXPLAIN`可验证是否使用索引: ```sql EXPLAIN SELECT * FROM orders WHERE product_id=305; ``` 观察`key`字段显示使用的索引,若为NULL则可能触发表 5. **释放时机** - 显式事务中:通过`COMMIT`或`ROLLBACK`释放 - 隐式事务中:语句执完成后立即释放 **优化建议**: 1. 为WHERE条件字段建立合适索引 2. 避免长事务(可通过`SHOW PROCESSLIST`监控) 3. 使用`SELECT ... FOR UPDATE NOWAIT`避免等待[^1] **状态验证**: ```sql SHOW OPEN TABLES WHERE In_use > 0; -- 查看表级 SELECT * FROM information_schema.INNODB_LOCKS; -- 查看(需开启监控) ``` 当使用`WHERE`条件涉及非唯一索引时,InnoDB会通过间隙(Gap Lock)防止幻读,这是其默认隔离级别(REPEATABLE READ)的特殊实现机制。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值