共计 5155 个字符,预计需要花费 13 分钟才能阅读完成。
这篇文章主要讲解了“MySQL 之 InnoDB 中锁的情况分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着丸趣 TV 小编的思路慢慢深入,一起来研究和学习“MySQL 之 InnoDB 中锁的情况分析”吧!
mysql select @@version;
+-----------+
| @@version |
+-----------+
| 5.7.21 |
+-----------+
1 row in set (0.01 sec)
一,锁的基本介绍
相对其他数据库而言,MySQL 的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM 和 MEMORY 存储引擎采用的是表级锁(table-level locking);InnoDB 存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
行级锁类型:
Record Lock(记录锁):当个记录的锁(锁住单条记录)
记录锁只会锁住索引的记录,如果 InnoDB 存储表在建立的时候没有任何索引,那么这个锁会使用隐式的主键来进行锁定,如下图
Gap Lock(间隙锁):锁定一个范围,不包括记录本身(只锁数据前面的 GAP)
如下图为的锁就是 GAP 锁,就是不允许其他事务在索引列 8 之前的间隙插入新的记录,也就是 (3 , 8) 这个区间。gap 锁 的作用仅仅是为了防止插入幻影记录的而已
Next-Key Lock(临键锁):同时锁住记录和记录前面的 GAP,也就是 Next-Key Lock = Record Lock + Gap Lock。
二,锁的分类
共享锁 Share Locks (简称 S 锁,属于行锁)
排它锁 Exclusive Locks (简称 X 锁,属于行锁)
意向共享锁 Intention Share Locks (简称 IS 锁,属于表锁)
意向排它锁 Intention Exclusive Locks (简称 IX 锁,属于表锁)
自增锁 AUTO-INC Locks(属于表锁)
下面具体介绍下每种类型的锁,我们先建一张 innodb 的表,sql 如下
create table tab_with_index(id int,name varchar(10)) engine=innodb;
alter table tab_with_index add index id(id);
insert into tab_with_index values(1, 1),(2, 2),(3, 3),(4, 4
共享锁
共享锁就是多个事务对于同一个数据可以共享一把锁,都能访问数据库,但是只能读不能修改;
事务 A:
select * from tab_with_index lock in share mode;
事务 B:
select * from tab_with_index where id =1; // 可以查询数据
update tab_with_index set name = aa where id = 1 ;
注意:这里的修改语句会堵塞住,直到事务 A 提交之后才能操作成功。
排它锁
排它锁不能与其他锁并存,如一个事务获取了一个数据行的排它锁,其他事务就不能在获取该行的锁,只有当前获取排它锁的事务可以对数据进行修改。(delete,update,create 默认是排它锁)
事务 A:
select * from tab_with_index where id =1 for update;
事务 B:
select * from tab_with_index where id =1; // 可以获取结果
select * from tab_with_index where id =1 for update; // 堵塞
select * from tab_with_index where id = 1 lock for share mode; // 堵塞
注意:事务 B 两个 sql 都会堵塞住, 也就是获取不到共享锁也获取不到排它锁,直到事务 A 提交之后才能操作成功。
意向共享锁和意向排它锁
意向共享锁:表示事务准备给数据行加入共享锁,也就是说一个数据行在加共享锁之前必须先获取该表的 IS 锁。
意向排它锁:表示事务准备给数据行加入排它锁,也就是说一个数据行加排它锁之前必须先获取该表的 IX 锁。
IS 锁和 IX 锁是表级锁,他们的提出仅仅为了在之后加表级别的 S 锁和 X 锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录,也就是说其实 IS 锁和 IX 锁是兼容的,IX 锁和 IX 锁是兼容的。《MySQL 是怎样运行的》
自增锁
针对自增列自增长的一个特殊的表级别锁。
show variables like innodb_autoinc_lock_mode
-- 默认值 1,代表连续,事务未提交则 ID 永久丢失
三,InnoDB 锁
1、事务及其 ACID 属性
事务是由一组 SQL 语句组成的逻辑处理单元,事务具有 4 属性,通常称为事务的 ACID 属性。
原子性(Actomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
2、并发事务带来的问题
相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多用户的并发操作,但与此同时,会带来一下问题:
脏读:一个事务正在对一条记录做修改,在这个事务并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做“脏读”
不可重复读:一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫做“不可重复读”。
幻读:一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”
上述出现的问题都是数据库读一致性的问题,可以通过事务的隔离机制来进行保证。
数据库的事务隔离越严格,并发副作用就越小,但付出的代价也就越大,因为事务隔离本质上就是使事务在一定程度上串行化,需要根据具体的业务需求来决定使用哪种隔离级别
脏读不可重复读幻读 read uncommitted√√√read committed
√√repeatable read
√serializable
可以通过检查 InnoDB_row_lock 状态变量来分析系统上的行锁的争夺情况:
mysql show status like innodb_row_lock%
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 18702 |
| Innodb_row_lock_time_avg | 18702 |
| Innodb_row_lock_time_max | 18702 |
| Innodb_row_lock_waits | 1 |
+-------------------------------+-------+
-- 如果发现锁争用比较严重,如 InnoDB_row_lock_waits 和 InnoDB_row_lock_time_avg 的值比较高
3、InnoDB 的行锁模式及加锁方法
共享锁(S):又称读锁(lock in share mode)。允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。若事务 T 对数据对象 A 加上 S 锁,则事务 T 可以读 A 但不能修改 A,其他事务只能再对 A 加 S 锁,而不能加 X 锁,直到 T 释放 A 上的 S 锁。这保证了其他事务可以读 A,但在 T 释放 A 上的 S 锁之前不能对 A 做任何修改。 排他锁(X):又称写锁(for update)。允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。若事务 T 对数据对象 A 加上 X 锁,事务 T 可以读 A 也可以修改 A,其他事务不能再对 A 加任何锁,直到 T 释放 A 上的锁。
mysql InnoDB 引擎默认的修改数据语句:update,delete,insert 都会自动给涉及到的数据加上排他锁,select 语句默认不会加任何锁类型,如果加排他锁可以使用 select …for update 语句,加共享锁可以使用 select … lock in share mode 语句。所以加过排他锁的数据行在其他事务中是不能修改数据的,也不能通过 for update 和 lock in share mode 锁的方式查询数据,但可以直接通过 select …from…查询数据,因为普通查询没有任何锁机制。
4、InnoDB 行锁实现方式
InnoDB 行锁是通过给索引上的索引项加锁来实现的,这一点 MySQL 与 Oracle 不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB 这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁!
1、在不通过索引条件查询的时候,innodb 使用的是表锁而不是行锁
create table tab_no_index(id int,name varchar(10)) engine=innodb;
insert into tab_no_index values(1, 1),(2, 2),(3, 3),(4, 4
session1session2set autocommit=0 select * from tab_no_index where id = 1;set autocommit=0 select * from tab_no_index where id =2select * from tab_no_index where id = 1 for update
select * from tab_no_index where id = 2 for update;
session1 只给一行加了排他锁,但是 session2 在请求其他行的排他锁的时候,会出现锁等待。原因是在没有索引的情况下,innodb 只能使用表锁。
2、创建带索引的表进行条件查询,innodb 使用的是行锁
create table tab_with_index(id int,name varchar(10)) engine=innodb;
alter table tab_with_index add index id(id);
insert into tab_with_index values(1, 1),(2, 2),(3, 3),(4, 4
session1session2set autocommit=0 select * from tab_with_indexwhere id = 1;set autocommit=0 select * from tab_with_indexwhere id =2select * from tab_with_indexwhere id = 1 for update
select * from tab_with_indexwhere id = 2 for update;
3、由于 mysql 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是依然无法访问到具体的数据(这里是表锁)
alter table tab_with_index drop index id;
insert into tab_with_index values(1, 4
session1session2set autocommit=0set autocommit=0select * from tab_with_index where id = 1 and name= 1 for update
select * from tab_with_index where id = 1 and name= 4 for update 虽然 session2 访问的是和 session1 不同的记录,但是锁的是具体表,所以需要等待锁
感谢各位的阅读,以上就是“MySQL 之 InnoDB 中锁的情况分析”的内容了,经过本文的学习后,相信大家对 MySQL 之 InnoDB 中锁的情况分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是丸趣 TV,丸趣 TV 小编将为大家推送更多相关知识点的文章,欢迎关注!