数据库的锁
关于mysql有哪些锁?锁的分类有哪些?可查看另外一篇博客:mysql 的锁
下面主要是记录在实际情景中的加锁情况进行分析
测试环境介绍
innodb 引擎下,隔离级别在 REPEATABLE READ 进行测试
表结构介绍
(1)简单的测试表一:study表,id为主键
create table study(
id int primary key,
name varchar(255),
age int
) engine=innodb;
insert study values(7 , 'g' , 21);
(2)简单的测试表二:study表,id为主键
create table my_table(
id int primary key,
year int
) engine=innodb;
insert into my_table values(1, 2000),(2,2005),(3,2007),(4,2010),(5,2012),(6,2017);
隔离级别的介绍
1、数据库的隔离级别:
能”表示能解决,“否”表示不能解决。
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
未提交读 | 否 | 否 | 否 |
提交读 | 能 | 否 | 否 |
可重复读 | 能 | 能 | 否 |
串行读 | 能 | 能 | 能 |
2、查询数据库的隔离级别
SELECT @@tx_isolation
3、修改数据库隔离级别:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ| SERIALIZABLE}
- 隔离级别名称可选值:READ UNCOMMITTED,READ COMMITTED,REPEATABLE READ,SERIALIZABLE
- global:设置全局session,但当前session不受影响。
- session,设置当前和之后的所有session。
- 都不写:设置当前session下一个还未开始的事务。
4、本测试环境使用的隔离级别
加锁情况介绍
具体查看这篇博客,这里大概还是列举一下锁有哪些:
注意:MyISAM引擎,如果需要进行锁表的操作,必须用lock table来进行,delete、update,insert语句会自动锁整个表
下面是对innodb引擎的锁类型进行介绍:
1、锁分类介绍:
(1)从锁的范围来分
- 行锁:记录锁、间隙锁、next-key 锁
- 表锁
(2)从对资源访问是否加锁来分:
- 乐观锁:不对资源加锁,只有在更新的时候才会判断数据会不会被修改,如果被修改,则修改失败
- 悲观锁:访问的时候就对资源加锁,加锁后别的访问不允许对其进行更新/删除操作
(3)从对数据的访问形式来分
- 读锁:共享读
- 写锁:独享写
2、加锁具体的实现方式
(1)默认的加锁及其对应加锁的类型
- 普通的select(没有加lock in share mode或for update)不会锁表或锁行的
- 常见的增删改(INSERT、DELETE、UPDATE)语句会自动对操作的数据行加写锁,其他对该数据进行操作的时候需要等待该事务提交后
(2)显式的加锁
- select xxxxx lock in share mode :加的是共享读锁,不会影响其他事务的读(这里指不加锁的读,可以再次加共享读锁,读上加写锁会失败),但会影响其他事务的写
- select xxxx for update : 加的是写锁,不加锁的读不会被影响,任意加锁的读会受影响,其他事务的写也会受影响
(3)举例:(在study表上进行测试)
举例一:测试共享读(lock in share mode)
事务一:
开启一个事务,对 id 为 7的加一个共享读锁(lock in share mode),不提交事务
事务二:
第一次尝试:开启一个事务,对 id 为 7的进行更新(会尝试获取到锁),操作不成功
第二次尝试:开启一个事务,也对 id 为 7的进行查询(不加锁的查询),操作成功
第三次尝试:开启一个事务,也对 id 为 7的进行查询(加共享读锁的查询),操作成功
第四次尝试:开启一个事务,也对 id 为 7的进行查询(加独享写锁的查询),操作失败
举例二:测试写锁(for update)
事务一:
开启一个事务,对 id 为 7的加一个写锁(for update),不提交事务
事务二:
第一次尝试:开启一个事务,对 id 为 7的进行更新(会尝试获取到锁),操作不成功
第二次尝试:开启一个事务,也对 id 为 7的进行查询(不加锁的查询),操作成功
第三次尝试:开启一个事务,也对 id 为 7的进行查询(加共享读锁的查询),操作失败(加写锁的读同理,也不会操作成功)
不同索引下的加锁问题
判断加不同的行锁时候加锁的类型是啥
(1)主键索引
上面举例介绍得到的一些总结,当id为主键的时候 对study 表的一些sql的加锁情况:
1. select * from study where id = 7; # 快照读,对其他的读和写没有影响
2. select * from study where id = 7 lock in share mode; # 加共享读锁,不影响其他的加共享读,但是影响加任意形式的写锁
3. select * from study where id = 7 for update; # 加写锁,影响加任意形式的读或者写锁
(2)非唯一索引
基于my_table这个表进行测试,目前的数据大小范围是**[2000 , 2017]**
第一个事务:
在year字段上加上一个普通的索引,然后进行between查询
create index idx on my_table(year);
start transaction;
select * from my_table where year between 2007 and 2010 for update;
第二个事务:
start transaction;
insert into my_table values(10 , 2004); =>Query OK,
delete from my_table where year=2005; =>Query OK,
insert into my_table values(11 ,2005); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(11, 2006); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(11, 2007); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(11, 2008); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(11, 2010); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(11, 2011); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
delete from my_table where year=2012; => ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(11, 2012); =>Query OK,
加锁分析:
(1)加锁范围:(2005,2012], 也就是在2007,2010,2012上各加了一个Next key lock(记录锁+间隙锁)
(3)非唯一索引,唯一查询
事务一:
开启事务,查询2010这条记录,目前这个记录是唯一的一条数据
start transaction;
select * from my_table where year =2010 for update;
事务一:
delete from my_table where year=2007 => Query OK
insert into my_table values(12 ,2007); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(12 , 2009); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into my_table values(12 ,2010); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into my_table values(12 ,2011); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> delete from my_table where year=2012; => Query OK
加锁分析:
(1)加锁范围:(2007,2012), 在2010上加了一记录 锁,在2007-2010,2010-2012上加了一个间隙锁
(4)唯一索引,唯一查询
创建唯一索引
drop index idx on my_table;
create unique index idx on my_table(year);
事务一:
开启事务,查询2010这条记录,目前这个记录是唯一的一条数据,year也是唯一索引
start transaction;
select * from my_table where year =2010 for update;
事务二
insert into my_table values(13, 2009); =>Query OK
insert into my_table values(14, 2010); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(15 ,2011); =>Query OK
加锁分析:只在2010上加了一个行锁(record锁)
(5)唯一索引,不唯一查询
事务一:
开启事务,between查询
start transaction;
select * from my_table where year between 2007 and 2010 for update;
事务二:
delete from my_table where year=2005; => Query OK
insert into my_table values(15, 2006); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(15, 2007); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(15, 2008); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(15, 2010); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(15, 2011); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(15, 2012); =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
delete from my_table where year=2012; =>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert into my_table values(15,2013); =>Query OK
加锁分析:
(1)加锁范围:(2005,2012], 也就是在2007,2010,2012上各加了一个Next key lock(记录锁+间隙锁),与非唯一索引between查询情况一致
(6)总结
a、只有字段是唯一索引且只有一条记录的情况下,不用加Gap锁,其他情况都要加Gap锁。
b、间隙Gap锁锁定的区域:根据检索条件向左寻找最靠近检索条件的记录值A,作为左区间,向右寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B)。
c、如果不加索引进行加锁的扫描,那就是全表扫描,直接就是加上表锁。不涉及到更细粒度的行锁了