Mysql锁详述

举报
LoneWalker、 发表于 2023/08/31 09:12:25 2023/08/31
【摘要】 Mysql锁详述

 前言

MySQL中不同的存储引擎支持不同的锁机制。比如

  • MyISAMMEMORY存储引擎采用的是表级锁(table-level locking);
  • BDB 存储引擎采用的是页面锁(page-level locking),但也支持表级锁;
  • InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。

从对数据操作的类型来看,可以分为:

  1. 读锁(共享锁):事务T对数据对象A加上读锁,则事务T只可以读A不能修改,其他事务也只能对数据A加读锁。
  2. 写锁(排它锁)事务T对数据对象A加上写锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。

一、表级锁、行级锁、页面锁的区别

  • 表级锁:偏向MyISM存储引擎,开销小,加锁块;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率最低,并发度最高。 InnoDB与MyISAM最大不不同有两点,一是支持事务,                         二 是采用了行级锁
  • 页面锁开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用; 而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

二、MyISAM表锁

2.1 查看表锁争用情况

  • Table_locks_immediate 表示立即释放表锁数
  • Table_locks_waited 表示需要等待的表锁数

如果waited值比较大,说明存在严重的表锁争用情况。

编辑

2.2 如何加表锁

MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATEDELETEINSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,如果需要显式加锁,必须同时取得所有涉及到表的锁。例如:
Lock tables 表1 read local, 表2 read local;
Select sum(total) from 表1;
Select sum(total) from 表2;
Unlock tables;
2.3 MyISAM锁调度

MyISAM 存储引擎的读锁和写锁是互斥的,读写操作是串行的。那么,一个进程请求某个 MyISAM 表的读锁,同时另一个进程也请求同一表的写锁,MySQL 如何处理呢?
答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插 到读锁请求之前!这是因为 MySQL 认为写请求一般比读请求要重要。这也正是 MyISAM 表 不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。

三、InnoDB锁问题

首先要知道InnoDB是支持事务的,对事务不理解的可以参考这篇:Mysql事务详解
InnoDB默认使用的是行锁:行锁开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度最高。

可以通过检查 InnoDB_row_lock 状态变量来分析系统上的行锁的争夺情况
编辑

除了读锁与写锁之外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

  • 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
  • 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。

示例SQL准备:

CREATE TABLE demo_innodb_lock(
   id INT(11),
	 name VARCHAR(16),
	 sex VARCHAR(1)
)ENGINE = INNODB DEFAULT CHARSET = utf8

INSERT INTO demo_innodb_lock VALUES (1,'100','1');
INSERT INTO demo_innodb_lock VALUES (1,'200','0');
INSERT INTO demo_innodb_lock VALUES (2,'90','0');
INSERT INTO demo_innodb_lock VALUES (3,'400','1');
INSERT INTO demo_innodb_lock VALUES (4,'300','0');
INSERT INTO demo_innodb_lock VALUES (5,'500','1');
INSERT INTO demo_innodb_lock VALUES (6,'600','0');
INSERT INTO demo_innodb_lock VALUES (7,'700','1');
INSERT INTO demo_innodb_lock VALUES (8,'800','0');

CREATE INDEX idx_id ON demo_innodb_lock(id);
CREATE INDEX idx_name ON demo_innodb_lock(name);

把自动提交关闭

SET autocommit = 0;

示例1:对于UPDATE、DELETE、INSERT语句,InnoDB会自动给涉及数据集加排他锁(X)   

编辑

此时由于事务没有提交,再开一个窗口对同一行进行更新操作,会发现处于阻塞状态

编辑

当第一个窗口中的事务执行commit;后,释放排它锁,如果第二个窗口中锁等待没有超时,则会立即执行

示例2:行锁升级为表锁

在示例准备中我们给id和name创建了索引,但是此时索引失效了,执行更新操作后不提交

编辑

在第二个窗口执行更新并提交

编辑

此时在第一个窗口中查询发现,id=4的数据并没有发生改变,问题是操作的不是同一行数据,说明此时已经升级为表锁了,原因就是索引失效。

编辑

提交窗口一中的事务,释放表锁,再次查询

编辑

3.1 InnoDB行锁的实现方式

  1. InnoDB行锁是通过给索引上的索引项加锁来实现的,InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
  2. 由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
  3. 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。
  4. 即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的,如果 MySQL 认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。

3.2 间隙锁Next-Key 锁)

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙
(GAP)”,InnoDB 也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key 锁)。
示例:

编辑

此时去执行这样一条更新语句时,由于数据库中没有id=2的数据,那么就意味着存在间隙。
编辑
窗口1中事务没提交的情况下,在窗口2插入一条ID=2的数据,发现插入不进去,证明了是存在间隙锁的;提交窗口1中的事务后,窗口2中SQL执行成功。

编辑


InnoDB 使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,另外一方面,是为了满足其恢复和复制的需要。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。