文章是通过《Mysql技术内幕 InnoDB存储引擎》这本书概括的,主要是锁的这一章,包括共享锁、排它锁、意向锁、一致性非锁定读、一致性锁定读、自增长与锁这些知识点,稍微有点长,耐心看下来,定会有收获。
测试表结构:
DROP TABLE IF EXISTS t
;
CREATE TABLE t
(
id
int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
BEGIN;
INSERT INTO t
VALUES (1);
INSERT INTO t
VALUES (2);
COMMIT;
开发多用户、数据库驱动应用时,最难的一个点就是:一方面要最大程度利用数据库的并发访问,另一方面还要确保每个用户能以一致的方式读取和修改数据,为此就有了锁机制。锁是为了支持资源进行并发访问,提供数据的完整性和一致性,Lock的对象是事务,用来锁定数据库中的对象,如表、页、行,事务仅在commit或rollback后释放。
InnoDB存储引擎实现两种标准的行级锁:
共享锁(S Lock):允许事务读一行数据
排它锁(X Lock):允许事务删除或更新一行数据
如果事务T1获取了行row1的共享锁,另一个事物T2同时也能获取row1的共享锁,这种情况成为锁兼容,但此时事务T3想获取row1的排它锁,则必须等事务T1、T2释放了row1的共享锁,这种称为锁不兼容。如图:
Mysql技术内幕.png
此外,InnoDB存储引擎支持多粒度(granular)锁定,这种锁允许事务在行级别和表级别上的锁同时存在,称之为意向锁。
意向共享锁(IS Lock): 事务获取一张表中某几行的共享锁
意向排它锁(IX Lock): 事务获取一张表中某几行的排它锁
Mysql技术内幕.png举例来说:如果已经有事务对表1加了IS表锁,之后事务对记录row1操作在表1上加IX锁,由于锁不兼容,需要等表1的IS锁操作完成后。
Mysql技术内幕.png
一致性非锁定读:
如果读取的行正在执行DELETE或UPDATE操作(X锁),这时读操作不会因此去等待行上的锁释放, Innodb存储引擎会去读取一个快照数据。快照数据是指该行之前版本的数据,读快照数据不需要上锁,因为没有事务要对历史的数据进行修改。
如图:
Mysql技术内幕.png
在事务隔离级别READ COMMITED(读已提交)和REPEATBLE READ(可重复读,InnoDB默认)下使用一致性非锁定读;READ COMMIT事务隔离级别下,非一致性锁读总是读取最新的快照;REPEATABLE READ隔离级别会去读取锁定行的上一次快照。
来看一个例子说明两种隔离级别不同的一致性非锁读的效果,打开两个数据库命令行(A和B):
Session A:
mysql> use test;
Database changed
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t where id = 1;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
会话A设置事务隔离级别为REPEATABLE READ,并且执行命令Begin开启了一个事务,查询了t表id等于1的记录,但事务并没有结束,与此同时在会话B继续操作
session B:
mysql> begin;
Query OK, 0 rows affected (0.01 sec)
mysql> update t set id = 10 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql>
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
会话B开启一个事物,修改id等于1的记录,commit提交会话B事务,再去会话A查看
Session A:
mysql> select * from t where id = 1;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
REPEATABLE READ隔离级别下,一致性非锁定读查询的是当前事务锁定行的上一次快照,并不是最新的快照,我们再来看看READ COMMIT事务隔离级别的一致性非锁读效果
Session A:
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t where id = 2;
+----+
| id |
+----+
| 2 |
+----+
1 row in set (0.00 sec)
会话A设置事务隔离级别为COMMIT READ,查询id为2的记录,切到会话B
Session B:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update t set id = 20 where id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
会话B开启一个事务修改id为2的值为20,并提交事务,再去会话A
Session A:
mysql> select * from t where id = 2;
Empty set
mysql> select * from t where id = 20;
+----+
| id |
+----+
| 20 |
+----+
1 row in set (0.00 sec)
效果已经出来了,COMMIT READ隔离级别下,一致性非锁定读获取的是最新的快照,之前id为2的快照不是最新的了,所以查询不到了,id为20的为最新快照。从数据库理论来看,其违反了事务ACID的I特性,及隔离性。
一致性锁定读
Innodb默认配置下,使用REPEATABLE READ隔离级别,Select操作会使用一致性非锁定读。但是在某些情况下,需要加锁保证数据逻辑的一致性,就要用到一致性锁定读。
InnoDB存储引擎对于SELECT语句支持两种一致性锁定读(locking read)操作:
1.SELECT *** FOR UPDATE(排它锁)
2.SELECT *** LOCK IN SHARE MODE(共享锁)
注意:使用FOR UPDATE时查询条件有索引列(主键也行,主键列默认有索引)的话,锁的是当前行,否则锁全表,update语句的where条件也是一个道理
两种操作必须在事务中,当事务提交了,锁也就释放了,因此在使用完两种锁定SELECT语句时,要使用BEGIN,START TRANSACTION或者AUTOCOMMIT=0来开启事务。
自增长与锁
InnoDB存储引擎中,每个含有自增的表进行插入操作时都会有一个自增长计数器(auto-increment counter),当对含有自增长计数器表进行插入时,计数器会被初始化,执行如下语句得到计数器的值:
SELECT MAX(auto_inc_col) from t FOR UPDATE
这种自增实际采用表锁机制,通过FOR UPDATE(X锁)排它锁来保证唯一,为了提高性能,锁不是在一个事务完成后释放,而是在完成自增长插入后的SQL后立即释放,这种实现方式称为AUTO-INC Locking。
由于AUTO-INC Locking的并发性能较差,事务必须等待前一个插入的完成(虽然不用等待事务的完成),对于大量数据插入会影响插入性能。从MySQL5.1.22版本开始,InnoDB存储引擎提供一种轻量级互斥量(mutex)的自增长机制,对内存中的计数器进行累加操作,大大提升列并发插入的性能。此版本开始,InnoDB提供了一个参数innodb_autoinc_lock_mode来控制自增长模式,该参数默认值是1。如图所示插入类型和innodb_autoinc_lock_mode值说明:
Mysql技术内幕.png Mysql技术内.png死锁
死锁的概念:死锁是指两个或两个一上的事务在执行过程中,因争夺资源而造成
的一种互相等待的现象。
解决死锁最简单的就是超时机制,当两个事务互相等待时,当一个等待时间超过了阈值,此事务就进行回滚,另一个事务就能继续进行了,超时机制虽然简单,若超时的事务占比较大,比如更新了很多行,占用了较多的undo log,这个事务回滚的时间相对另一个事务所占的时间可能会很多。当前数据库普遍采用wait-for graph(等待图)的方式 进行死锁检测。
Mysql技术内幕.pngwait-for graph是一种主动的死锁监测机制,在每个事务请求锁并发等待时都会判断是否存在回路,若存在回路则有死锁(如图t1和t2事务存在回路),InnoDB 存储引擎选择回滚undo log量最小的事务。
死锁示例(注意查询条件列要带有索引,不然会锁全表):
Mysql技术内幕.png
结束语:如果有写的不好或者不太懂得地方可以在下方评论,感觉不错可以点个赞哦。
网友评论