Msql锁机制

MySQL锁机制是为了保证并发操作的数据一致性而设计的。在并发环境下,多个事务同时访问同一数据,如果不加控制,就会导致数据的不一致性。

MySQL的锁可以分为行级锁和表级锁。

  1. 行级锁:在对表中的数据进行修改时,MySQL会自动加上行级锁,该锁只针对修改的行有效,其他行不受影响。行级锁的优点是精度高,缺点是占用资源多,容易造成死锁。InnoDB行锁是通过给索引上的索引项加锁 来实现的,没有索引的情况下,InnoDB仍然可以使用行级锁。不过,由于在没有索引的情况下需要扫描整个表,因此会对性能产生较大的影响,同时会将锁定作用于整个表,从而影响并发性能和可伸缩性

    使用行级锁定的主要是 InnoDB 存储引擎。

  2. 表级锁:在对整个表进行操作时,MySQL会自动加上表级锁,该锁可以防止其他事务修改该表。表级锁的优点是简单高效,缺点是并发度低,容易造成阻塞。

    使用表级锁定的主要是 MyISAMMEMORYCSV 等一些非事务性存储引擎。

InnoDB 引擎实现的锁机制

记录锁(Record LOCK)

Record Lock表示记录锁,锁的是索引记录。

记录锁是 InnoDB 存储引擎实现多版本并发控制(MVCC)机制的重要基础,用来保证数据的一致性和并发性。在 MVCC 中,每个事务在执行修改操作时都会为所修改的数据行创建一个新的版本,并且使用记录锁来保护当前事务对该数据行的修改操作。

共享锁(Shared Lock)

共享锁(S锁)是一种共享锁定,它允许多个事务同时读取一行数据,但防止这些事务对该行数据进行修改,保证数据的一致性和隔离性。

1
2
-- 加共享锁(S)
select * from table_name where ... lock in share mode
排它锁(Exclusive Lock)

排它锁(X锁)是一种排它锁定,它防止其他事务读取或修改该行数据,保证了数据的独占性。如果一个事务已经持有了一个行的 X 锁,其他的事务就不能再持有该行的任何锁,包括共享锁和排它锁。

1
2
-- 加排它锁(X)
select * from table_name where ... for update

间隙锁(GAP LOCK)

Gap Lock是间隙锁,锁的是索引记录之间的间隙。

间隙锁是 InnoDB 存储引擎实现的一种锁机制,它可以在多版本并发控制(MVCC)下防止幻读问题的发生。在 RR 隔离级别下,InnoDB 存储引擎可以使用 MVCC 机制来保证读取的数据的可重复性,并且可以使用间隙锁来避免幻读问题的发生。

间隙锁是针对索引上的间隙(即索引记录之间的空隙)进行加锁,用来防止其他事务在这些间隙中插入新的数据,从而保证了查询结果的一致性。在 RR 隔离级别下,InnoDB 存储引擎使用间隙锁机制来支持幻读问题的解决。

举例来说,假如 emp 表中只有 101 条记录,其 empid 的值分别是1, 2, …, 100, 101,下面的SQL:

1
SELECT * FROM emp WHERE empid > 100 FOR UPDATE

当我们用条件检索数据,并请求共享或排他锁时,InnoDB 不仅会对符合条件的 empid 值为 101 的记录加锁,也会对 empid 大于 101(这些记录并不存在)的 “间隙” 加锁。

这个时候如果你插入 empid 等于 102 的数据的,如果那边事物还没有提交,那你就会处于等待状态,无法插入数据。

使用方式

  1. 使用 SELECT … FOR UPDATE 语句:可以在 SQL 查询语句中使用 SELECT … FOR UPDATE 语句,该语句会在查询时对索引进行间隙锁定。例如:

    1
    SELECT * FROM table_name WHERE col BETWEEN 10 AND 20 FOR UPDATE;

    该语句会对 col 列值在 10 和 20 之间的行进行间隙锁定,防止其他事务在该间隙内插入新的数据。

  2. 使用 SELECT … LOCK IN SHARE MODE 语句:该语句与 SELECT … FOR UPDATE 语句类似,但它会对索引进行共享锁定。例如:

    1
    SELECT * FROM table_name WHERE col BETWEEN 10 AND 20 LOCK IN SHARE MODE;

    该语句会对 col 列值在 10 和 20 之间的行进行共享锁定,防止其他事务在该间隙内修改数据。

临键锁

Next-Key Lock是Record Lock和Gap Lock的组合,同时锁索引记录和间隙。他的范围是左开右闭的。

临键锁可以理解为锁住的是索引本身以及索引之前的间隙,是一个左开右闭的区间。当 SQL 执行按照非唯一索引进行数据的检索时,会给匹配到行上加上临键锁。

示例:

范围:(负无穷大,10]

事务A:

1
2
begin;
select * from sys_user where age=10 for update;

事务B:

1
2
begin;
insert into sys_user (id, name, name_pinyin, id_card, phone, age)values (5, '小六', 'xiaoliu', 300000004, 13000008000, 9);

结果:
事务B被阻塞,无法被插入。

事务C:

1
2
begin;
insert into sys_user (id, name, name_pinyin, id_card, phone, age)values (5, '小六', 'xiaoliu', 300000004, 13000008000, 11);

结果:
事务C正常插入,无阻塞。

意向锁

意向锁是 InnoDB 存储引擎实现的一种锁机制,它用来协调事务对表和分区的锁定。

意向锁是一种表级别的锁,它可以分为意向共享锁(IS)和意向排它锁(IX)两种类型。意向共享锁(IS)表示一个事务想要对表或分区进行共享锁定,意向排它锁(IX)表示一个事务想要对表或分区进行排它锁定。当一个事务想要对表或分区加锁时,它需要先获取对应的意向锁,以通知其他事务该表或分区的锁定状态。

在 InnoDB 存储引擎中,一个事务想要对一个数据行加锁时,它需要先获取该数据行所在的表或分区的意向锁,然后再获取该数据行的行级锁。这种方式可以减少锁的竞争,提高系统的并发性能。

需要注意的是,意向锁并不是用来保证数据一致性的锁,而是用来协调事务对表和分区的锁定,避免锁的冲突和竞争。

我们可以举个生活中的例子,再来理解下为什么需要存在意向锁。

打个比方,就像有个游乐场,很多小朋友进去玩,看门大爷如果要下班锁游乐场的门(加表锁),他必须确保每个角落都要去检查一遍,确保每个小朋友都离开了(释放行锁),才可以锁门。

假设锁门是件频繁发生的事情,大爷就会非常崩溃。那大爷想了一个办法,每个小朋友进入,就把自己的名字写在本子上,小朋友离开,就把自己的名字划掉,那大爷就能方便掌握有没有小朋友在游乐场里,不必每个角落都去寻找一遍。例子中的“小本子”,就是意向锁,他记录的信息并不精细,他只是提醒大爷,是否有人在屋里。

死锁

MySQL死锁是指两个或多个事务相互等待对方释放锁资源的状态,导致事务无法继续执行,造成数据库的阻塞。MySQL死锁是一个常见的并发问题,特别是在高并发环境下容易发生。

MyISAM表锁是deadlock free的,这是因为MyISAM总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了在InnoDB中发生死锁是可能的。

造成死锁的原因主要有以下几个:

  1. 竞争资源:多个事务同时竞争同一资源(如行、表等),导致相互等待。
  2. 事务执行顺序不一致:当两个事务在相互等待时,如果它们执行的顺序不一致,就会出现死锁。
  3. 数据库事务隔离级别设置不当:如果事务隔离级别设置过高,可能会导致死锁的发生。

为了避免MySQL死锁,我们可以采取以下措施:

  1. 减少事务时间:事务执行时间越长,死锁的概率就越大。因此,我们应该尽可能地减少事务的执行时间。
  2. 合理使用索引:合理地使用索引可以减少行级锁的竞争,降低死锁的发生率。
  3. 设置合适的事务隔离级别:在选择事务隔离级别时,应根据业务需求来选择,避免设置过高的隔离级别。
  4. 控制并发度:通过控制并发度,可以降低锁竞争的概率,减少死锁的发生。
  5. 定期优化数据库表结构:定期优化表结构可以降低锁竞争的概率,从而减少死锁的发生。
  6. 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。
  7. 在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。

如果出现死锁,通过 MySQL 提供的 SHOW ENGINE INNODB STATUS 命令可以查看当前 MySQL 的锁状态。该命令会输出包含当前所有事务、锁等信息的大量文本,需要找到其中的“LATEST DETECTED DEADLOCK”部分,查看最近发生的死锁信息。


Msql锁机制
https://cason.work/2023/04/09/Msql锁机制/
作者
Cason Mo
发布于
2023年4月9日
许可协议