hangkk2008

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
测试数据准备:
CREATE TABLE t1_lock
(
id int unsigned not null auto_increment,
vid int unsigned not null default 0,
name varchar(10) not null default '',
other varchar(20) not null default '',
primary key(id),
key vid(vid)
) engine =InnoDB;

insert into t1_lock(vid,name,other)
values(1,'xiaoli','My name is xiaoli'),(2,'xiaozhang','My name is xiaozhang'),(3,'dog','My name is dog'),(4,'cat','My name is cat'),(4,'fish','My name is fish');


root@localhost:mysql.sock  23:08:51 [school]>root@localhost:mysql.sock  23:08:51 [school]>show index from t1_lock;
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table   | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type |
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| t1_lock |          0 | PRIMARY  |            1 | id          | A         |           2 |     NULL | NULL   |      | BTREE      |         |               |
| t1_lock |          1 | vid      |            1 | vid         | A         |           2 |     NULL | NULL   |      | BTREE      |         |               |
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
2 rows in set (0.00 sec)

 

查看事务,锁,锁等待的系统表
select
* from INFORMATION_SCHEMA.innodb_trx; select * from INFORMATION_SCHEMA.innodb_locks; select * from INFORMATION_SCHEMA.innodb_lock_waits;

测试1:一致性非锁定读

      一致性的非锁定行读(consistent nonlocking read)是指InnoDB存储引擎通过行多版本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行DELETE、UPDATE操作,这是读取操作不会因此而会等待行上锁的释放,相反,InnoDB会去读取行的一个快照数据。

      之所以称其为非锁定读,因为不需要等待访问的行上X锁的释放。快照数据是指改行之前版本的数据,该实现是通过undo段来实现的。但是在不同事务隔离级别下,读取的方式不同,并不是每个事务隔离级别下读取的都是一致性读。

例如:

     对于read committed的事务隔离级别,他总是读取行的最新版本,如果行被锁定了,则读取该行版本的最新一个快照。

     对于repeatable read(innoDB存储引擎的默认隔离级别),总是读取事务开始时的行数据。

     非锁定读的机制大大提高了数据读取的并发性,在Innodb存储引擎默认设置下,这是默认的读取方式,但是在某些情况下,可以对读进行加锁,比如:

     1、显式对读进行加锁,如使用 select --- for update ;select --- lock in share mode

     2、在外键的插入和更新上,因为在外键的插入和更新上,对于数据的隔离性要求较高,在插入前需要扫描父表中的记录是否存在,所以,在外键的插入删除上,InnoDB会使用加S锁的方式来实现。

session1:
root@localhost:mysql.sock  23:11:17 [school]>begin;
#id =1 记录加排他锁
root@localhost:mysql.sock  23:11:23 [school]>select * from t1_lock where id=1 for update;
+----+-----+--------+-------------------+
| id | vid | name   | other             |
+----+-----+--------+-------------------+
|  1 |   1 | xiaoli | My name is xiaoli |
+----+-----+--------+-------------------+
1 row in set (0.08 sec)
session2:
root@localhost:mysql.sock  23:11:10 [school]>begin;
#以下查询不会被柱塞,这是一致性非锁定读
root@localhost:mysql.sock  23:11:49 [school]>select * from t1_lock where id=1;
+----+-----+--------+-------------------+
| id | vid | name   | other             |
+----+-----+--------+-------------------+
|  1 |   1 | xiaoli | My name is xiaoli |
+----+-----+--------+-------------------+
1 row in set (0.00 sec)

#以下查询会被柱塞,加共享锁和session1的排它锁互斥 root
@localhost:mysql.sock 23:12:12 [school]>select * from t1_lock where id=1 lock in share mode; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 测试2:无索引列查询

session1:
root@localhost:mysql.sock  23:36:30 [school]>begin;
root@localhost:mysql.sock  23:36:37 [school]>select * from t1_lock where name='xiaoli' for update;
+----+-----+--------+-------------------+
| id | vid | name   | other             |
+----+-----+--------+-------------------+
|  1 |   1 | xiaoli | My name is xiaoli |
+----+-----+--------+-------------------+
1 row in set (0.11 sec)

session2:
root@localhost:mysql.sock  23:37:07 [school]>begin;
root@localhost:mysql.sock  23:37:10 [school]>select * from t1_lock where name='xiaozhang' for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 查询列上没有索引时,锁被升级(RR,检索条件没有索引时,会导致锁扩大,全部行记录都被锁定,并且加gap),全部 name  的记录都会被锁定.

测试3:一个条件有索引,另一个条件没有索引

session1:
root@localhost:mysql.sock  23:50:55 [school]>begin;
root@localhost:mysql.sock  23:50:57 [school]>select * from t1_lock where vid=1 and name='xiaoli' for update;
+----+-----+--------+-------------------+
| id | vid | name   | other             |
+----+-----+--------+-------------------+
|  1 |   1 | xiaoli | My name is xiaoli |
+----+-----+--------+-------------------+
1 row in set (0.13 sec)

session2:
root@localhost:mysql.sock  23:50:47 [school]>begin;
root@localhost:mysql.sock  23:51:27 [school]>select * from t1_lock where vid=1 and name='xiaozhang' for update; 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
注意到vid = 1的时候,name = 'xiaoli',只有这一条记录.
但是session2检索的条件,vid = 1 and name = 'xiaozhang'  这样的记录并不存在,但是被锁定了,因为vid上有索引而name上没有索引,锁扩大锁定vid=1的所有记录.
如果查询vid=2 and name='xiaozhang' for update ,则不会锁定.(如测试4)

 测试4:一个条件有索引,另一个条件没有索引

session1:
root@localhost:mysql.sock  00:13:13 [school]>begin;
root@localhost:mysql.sock  00:13:17 [school]>select * from t1_lock where vid=1 and name='xiaoli' for update;
+----+-----+--------+-------------------+
| id | vid | name   | other             |
+----+-----+--------+-------------------+
|  1 |   1 | xiaoli | My name is xiaoli |
+----+-----+--------+-------------------+
1 row in set (0.08 sec)

session2:
root@localhost:mysql.sock  00:13:08 [school]>begin;
root@localhost:mysql.sock  00:13:28 [school]>select * from t1_lock where vid=2 and name='xiaoli' for update;    
Empty set (0.07 sec)

session2没有被session1柱塞

 测试5:间隙锁(存在于RR隔离级别),目的防止幻读,对于插入和更新操作生效

session1:
oot@localhost:mysql.sock  23:49:48 [school]>begin;
root@localhost:mysql.sock  23:49:52 [school]>update t1_lock set name='s02'  where vid=2;
#此处会在辅助索引vid=2上加排他锁及间隙锁(锁定范围:[1,2,3]),同时对应主键上也会加行锁
session2:
#vid=1 在间隙锁之内,被阻塞 root
@localhost:mysql.sock 23:50:53 [school]>begin; root@localhost:mysql.sock 23:50:56 [school]>insert into t1_lock(vid,name,other) values(1,'s01','s01'); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

#vid=2 在间隙锁之内,被阻塞

  root@localhost:mysql.sock 23:58:37 [school]>begin;

  root@localhost:mysql.sock 23:59:08 [school]>insert into t1_lock(vid,name,other) values(2,'s02','s02');
  ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

#vid=3 在间隙锁之内,被阻塞
root@localhost:mysql.sock  23:51:49 [school]>begin;
root@localhost:mysql.sock  23:51:52 [school]>insert into t1_lock(vid,name,other) values(3,'s03','s03');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

#vid=4 不在间隙锁之内,没有被阻塞
root@localhost:mysql.sock  23:52:53 [school]>begin;
root@localhost:mysql.sock  23:56:15 [school]>insert into t1_lock(vid,name,other) values(4,'s04','s04');
Query OK, 1 row affected (0.01 sec)
root@localhost:mysql.sock  23:56:22 [school]>commit;
Query OK, 0 rows affected (0.00 sec)

 

 锁解释:

 1.Record Lock 单个记录上的锁

 2.Gap Lock 间隙锁,锁定一个范围,但不包含记录本身(gap Lock为了解决幻读问题,只有在RR隔离级别下才有间隙锁)

 3.Next-key Lock,锁定一个范围和本身 Record Lock + Gap Lock

 

 innodb 锁的特点:

 1.主键索引是record lock(RR隔离级别下)
 2.唯一辅助索引是record lock(RR隔离级别下)
 3.普通辅助索引是next-key lock(RR隔离级别下)
 4.没有索引的话,则是全表范围next-key lock(RR隔离级别下)
 5.RC隔离级别下,只有record lock
 6.RR & innodb_locks_unsafe_for_binlog = 1,只有record lock

 

 详细测试gap锁

测试数据
CREATE TABLE t_gap( a INT, b INT, PRIMARY KEY (a), KEY(b));
insert into t_gap values(1,1), (3,1), (5,3), (7,6),(10,8);
root@localhost:mysql.sock 00:19:34 [school]>select * from t_gap;
+----+------+
| a | b |
+----+------+
| 1 | 1 |
| 3 | 1 |
| 5 | 3 |
| 7 | 6 |
| 10 | 8 |
+----+------+
5 rows in set (0.00 sec)

 gap测试

session1:
T1:begin;SELECT  *  FROM  t_gap WHERE b =3 FOR UPDATE;
| 5  | 3  |   这条记录
T1 会加上如下锁:

b = 3, X, next-key lock,间隙锁范围[1,6]
a = 5, X , record lock
session2: T2:
begin; SELECT * FROM t_gap WHERE A=5 LOCK IN SHARE MODE; 会等待
T2:begin;INSERT INTO t_gap SELECT 4,2; 
会等待
T2:begin;INSERT INTO t_gap SELECT 6,5;
会等待
T2:begin;INSERT INTO t_gap SELECT 2,2; 
会等待
T2:begin;INSERT INTO t_gap SELECT 4,6; 
会等待
如果此例中的b列上唯一性的所有,则T2都不会等待,因为此时T1使用行锁只会锁定一条记录.

 

上面同样的sessoion1
session2:
T2:begin;INSERT INTO t_gap SELECT 8,6;
没有被柱塞,原因是8,6 不在gap锁之间,它在7,6之外,如果插入(2,6),(4,6),(6,6)就会被阻塞
T2:begin;INSERTINTO t_gap SELECT2,0;  
没有被阻塞
T2:begin;INSERT INTO t_gap SELECT 6,7;  
没有被阻塞

 

 

 

posted on 2016-06-09 15:58  鱼儿也疯狂  阅读(203)  评论(0)    收藏  举报