由于Innodb默认的隔离级别是 Repeatable Read,接下来都是以该级别为基础进行分析;
创建数据库test,数据库表user:id是主键,phone是唯一索引,age是非唯一索引。
mysql> show create table user\G;
*************************** 1. row ***************************
Table: user
Create Table: CREATE TABLE `user` (
`id` int(4) NOT NULL DEFAULT '0',
`age` int(4) DEFAULT NULL,
`phone` int(20) DEFAULT NULL,
`name` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `phone` (`phone`),
KEY `idx_k` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
mysql> select * from user;
+----+------+-------+-------+
| id | age | phone | name |
+----+------+-------+-------+
| 3 | 12 | 122 | wang |
| 8 | 12 | 123 | zhang |
| 14 | 15 | 128 | zhang |
+----+------+-------+-------+
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update user set name='www' where name='wang';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update user set name='zzz'where id=8;
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into user values(2,8,111,'li');
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update user set name='li' where id = 7;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
分析
首先看innodb_trx表,查看具体事务情况,可以看到执行了四个事务,通过查看trx_query得知,56D为更新id为8的操作,56E为插入id为2的操作,这两个事务的trx_started都未LOCK WAIT状态,说明都给堵塞住了,当然通过事务的执行时间也可以将四个事务进行区分。
mysql> select * from information_schema.innodb_trx\G;
*************************** 1. row ***************************
trx_id: 56F
trx_state: RUNNING
trx_started: 2017-11-12 03:18:16
trx_requested_lock_id: NULL
trx_wait_started: NULL
trx_weight: 2
trx_mysql_thread_id: 69
trx_query: NULL
trx_operation_state: NULL
trx_tables_in_use: 0
trx_tables_locked: 0
......
*************************** 2. row ***************************
trx_id: 56E
trx_state: LOCK WAIT
trx_started: 2017-11-12 03:17:54
trx_requested_lock_id: 56E:0:327:9
trx_wait_started: 2017-11-12 03:17:54
trx_weight: 2
trx_mysql_thread_id: 65
trx_query: insert into user values(2,8,111,'li')
trx_operation_state: inserting
trx_tables_in_use: 1
trx_tables_locked: 1
trx_lock_structs: 2
......
*************************** 3. row ***************************
trx_id: 56D
trx_state: LOCK WAIT
trx_started: 2017-11-12 03:17:30
trx_requested_lock_id: 56D:0:327:7
trx_wait_started: 2017-11-12 03:18:24
trx_weight: 2
trx_mysql_thread_id: 63
trx_query: update user set name='zzz'where id=8
trx_operation_state: starting index read
trx_tables_in_use: 1
trx_tables_locked: 1
trx_lock_structs: 2
......
*************************** 4. row ***************************
trx_id: 56C
trx_state: RUNNING
trx_started: 2017-11-12 03:17:05
trx_requested_lock_id: NULL
trx_wait_started: NULL
trx_weight: 3
trx_mysql_thread_id: 68
trx_query: NULL
trx_operation_state: NULL
trx_tables_in_use: 0
trx_tables_locked: 0
......
4 rows in set (0.00 sec)
然后看innodb_lock_waits表,查看锁的等待情况,可以看出事务id为56C持有的锁将56D有56E都给堵塞住了;
mysql> select * from information_schema.innodb_lock_waits\G;
*************************** 1. row ***************************
requesting_trx_id: 56E
requested_lock_id: 56E:0:327:9
blocking_trx_id: 56C
blocking_lock_id: 56C:0:327:9
*************************** 2. row ***************************
requesting_trx_id: 56D
requested_lock_id: 56D:0:327:7
blocking_trx_id: 56C
blocking_lock_id: 56C:0:327:7
2 rows in set (0.00 sec)
接下来看innodb_locks表,分析造成阻塞的具体锁的类型,可以看到56D需要的X锁,此时被56C占有,而56E需要X和GAP锁,此时锁也被56C占有。
mysql> select * from information_schema.innodb_locks\G;
*************************** 1. row ***************************
lock_id: 56E:0:327:9
lock_trx_id: 56E
lock_mode: X,GAP
lock_type: RECORD
lock_table: `test`.`user`
lock_index: `PRIMARY`
lock_space: 0
lock_page: 327
lock_rec: 9
lock_data: 3
*************************** 2. row ***************************
lock_id: 56C:0:327:9
lock_trx_id: 56C
lock_mode: X
lock_type: RECORD
lock_table: `test`.`user`
lock_index: `PRIMARY`
lock_space: 0
lock_page: 327
lock_rec: 9
lock_data: 3
*************************** 3. row ***************************
lock_id: 56D:0:327:7
lock_trx_id: 56D
lock_mode: X
lock_type: RECORD
lock_table: `test`.`user`
lock_index: `PRIMARY`
lock_space: 0
lock_page: 327
lock_rec: 7
lock_data: 8
*************************** 4. row ***************************
lock_id: 56C:0:327:7
lock_trx_id: 56C
lock_mode: X
lock_type: RECORD
lock_table: `test`.`user`
lock_index: `PRIMARY`
lock_space: 0
lock_page: 327
lock_rec: 7
lock_data: 8
4 rows in set (0.00 sec)
最后分析:由于innodb行锁的前提是对索引加锁,而name并没有建立索引,索引这个时候是对user表加了表锁,由于执行的是更新操作,那么进行的是当前读,从56C堵塞了56D可以得知,表锁包含了对所有的记录加的是X锁,而56C将56E堵塞了,说明表锁对所有的间隙加了GAP锁,而56C并没有堵塞56F,说明间隙锁不会阻止其他事务在该间隙上继续获得间隙锁。
总结:对非索引字段进行当前读操作时,那么会对该表加表锁,表锁包括对表中所有记录加排它锁,同时对所有记录的间隙加间隙锁。间隙锁不会阻止不同的事务在同一间隙上获得,只是阻止其他事务插入到当前间隙当中。
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user where id=3;
+----+------+-------+------+
| id | age | phone | name |
+----+------+-------+------+
| 3 | 12 | 122 | wang |
+----+------+-------+------+
1 row in set (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update user set age = 10 where id=3;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user where id=3;
+----+------+-------+------+
| id | age | phone | name |
+----+------+-------+------+
| 3 | 12 | 122 | wang |
+----+------+-------+------+
1 row in set (0.00 sec)
mysql> select * from user where id=3 for update;
+----+------+-------+------+
| id | age | phone | name |
+----+------+-------+------+
| 3 | 10 | 122 | wang |
+----+------+-------+------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update user set name='www' where id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from user where id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into user values(2,8,111,'lll');
Query OK, 1 row affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update user set name='zzz' where id = 8;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
分析
结合innodb_lock_waits、innodb_locks、innodb_trx三个表及上面的结果分析可以,通过条件为主键索引进行操作时,并不影响其他记录及间隙操作,只影响了该主键所在记录的操作,说明主键索引操作为行排它锁。
相关阅读:
本文来自网易实践者社区,经作者王志泳授权发布。