playMySQL

sql lock analysis

事前准备

CREATE TABLE hero (
    number INT,
    name VARCHAR(100),
    country varchar(100),
    PRIMARY KEY (number),
    KEY idx_name (name)
) Engine=InnoDB CHARSET=utf8;

INSERT INTO hero VALUES
    (1, 'l刘备', '蜀'),
    (3, 'z诸葛亮', '蜀'),
    (8, 'c曹操', '魏'),
    (15, 'x荀彧', '魏'),
    (20, 's孙权', '吴');

语句加锁分析

把语句分为3种大类:普通的SELECT语句、锁定读的语句、INSERT语句,我们分别看一下。

普通的SELECT语句

REPEATABLE READ隔离级别下, InnoDB中的MVCC并不能完完全全的禁止幻读。

# 事务T1,REPEATABLE READ隔离级别下
mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.01 sec)
mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.03 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM hero WHERE number = 30;
Empty set (0.01 sec)

# 此时事务T2执行了:INSERT INTO hero VALUES(30, 'g关羽', '魏'); 并提交

mysql> select * from hero;
+--------+------------+---------+
| number | name       | country |
+--------+------------+---------+
|      1 | l刘备      | 蜀      |
|      3 | z诸葛亮    | 蜀      |
|      8 | c曹操      | 魏      |
|     15 | x荀彧      | 魏      |
|     20 | s孙权      | 吴      |
+--------+------------+---------+

mysql> UPDATE hero SET country = '蜀' WHERE number = 30;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM hero WHERE number = 30;
+--------+---------+---------+
| number | name    | country |
+--------+---------+---------+
|     30 | g关羽   | 蜀      |
+--------+---------+---------+
1 row in set (0.01 sec)

在REPEATABLE READ隔离级别下,T1第一次执行普通的SELECT语句时生成了一个ReadView,之后T2向hero表中新插入了一条记录便提交了, ReadView并不能阻止T1执行UPDATE或者DELETE语句来对改动这个新插入的记录(因为T2已经提交,改动该记录并不会造成阻塞), 但是这样一来这条新记录的trx_id隐藏列就变成了T1的事务id,之后T1中再使用普通的SELECT语句去查询这条记录时就可以看到这条记录了, 也就把这条记录返回给客户端了

SERIALIZABLE隔离级别下,需要分为两种情况讨论:

锁定读的语句

我们把下边四种语句放到一起讨论:

我们说语句一和语句二是MySQL中规定的两种锁定读的语法格式,而语句三和语句四由于在执行过程需要首先定位到被改动的记录并给记录加锁, 也可以被认为是一种锁定读。

READ UNCOMMITTED/READ COMMITTED隔离级别下

在READ UNCOMMITTED下语句的加锁方式和READ COMMITTED隔离级别下语句的加锁方式基本一致,所以就放到一块儿说了。 值得注意的是,采用加锁方式解决并发事务带来的问题时,其实脏读和不可重复读在任何一个隔离级别下都不会发生(因为读-写操作需要排队进行)。

对于使用主键进行等值查询的情况

对于使用主键进行范围查询的情况

对于使用二级索引进行等值查询的情况

对于使用二级索引进行范围查询的情况

全表扫描的情况

比方说:

由于country列上未建索引,所以只能采用全表扫描的方式来执行这条查询语句,存储引擎每读取一条聚簇索引记录,就会为这条记录加锁一个S型正常记录锁, 然后返回给server层,如果server层判断country = ‘魏’这个条件是否成立,如果成立则将其发送给客户端,否则会释放掉该记录上的锁,画个图就像这样: