目录
前言
锁是计算机协调多个进程或多线程并发访问某一资源的同步机制,数据库设计到数据的安全性,所以锁的使用是不可避免的。
1、锁分类
-
悲观锁-Pessimistic lock
-
全局锁:flush table with read lock;使用全局锁会锁住整个数据库,使其处于只读状态;
-
表锁: lock table和 意向锁(Intention Locks) MataData Lock ,意向锁不用显示调用;
-
行锁(Record-lock)
-
间隙锁( gap locks)
-
临键锁( next-key lock) ,由行锁和间隙锁组成;
-
-
乐观锁- Optimistic Lock
-
自旋cas机制;可通过version版本和时间戳来判断;
-
2、全局锁
2.1 全局锁:
flush tables with lock:全局锁主要是对整个数据库的实例加锁,一个库被全局锁锁住后处于只读状态,以下动作将会被禁止:
-
DML:data manipulation language 对数据的增查改删更新操作;
-
DDL: data definition language ,包括对表的修改,增加字段删除字段;
使用场景:全库逻辑备份。如果没有全局锁,那么系统备份得到的数据不是一个逻辑时间点的,这个视图的逻辑是不一致的,数据备份会出现问题。
3、表锁
3.1 lock tables 表锁
-
加锁:lock table tableName read/write;
-
解锁:unlock table:
使用场景
:在没有行锁这种粒度锁的时候,处理并发的时候就会使用表级锁;
lock table car write;
select * from car;
unlock tables;
特点:
-
(1). 可以主动unlock tables释放,在客户端断开连接的时候自动释放;
-
(2). lock tables除了限制别的线程的读写外,也限定了本线程接下来的操作对象;
例如线程执行:
lock tables t1 read, t2 write; //除了阻塞其他线程外,自己本身也只能读t1,读写t2,不能写t1.
3.2 MDL-意向锁
Mate Data Lock,
意向锁
-也称为
元数据锁
,在mysql5.5版本中引入了MDL,引擎自己维护,用户不用手动操作。当对一个表做增查改删的时候+MDL读锁,当要对表结果做变更的时候,+MDL写锁。
-
意向共享锁:如果一张表上面至少有一个意向共享锁,说明有其他的事务给其中的某些数据行加上了共享锁;
-
意向排他锁:如果一张表上面至少有一个意向排他锁,说明有其他的事务给其中的某些数据行加上了排他锁;
作用:
(1)是防止DDL和MDL之间冲突,保证DDL-(数据定义语言)和DML-(操纵语言:增查改删)的数据一致性;
-
MDL的(增查改删)读锁和 DDL之间不互斥;
-
MDL的写锁和DDL的写锁修改表结构之间互斥;在对表结构进行修改时,加MDL锁,另外线程只能等待。
(2)提高加锁的效率;
-
加x锁的前提是:表中没有任何锁,包括行锁的存在,意向锁避免了加行锁时检查锁状态的全表行锁扫描,先加意向锁后加x锁,它相当于一个标记,提高了加锁的效率;
4、行锁【Innodb】
4.1、行锁实现原理
-
行锁是存储引擎innodb自己设计的,不是所有的存储引擎都支持,比如myisam就不支持;
-
行锁是基于索引实现的,所以没有建立索引的更新将会扫描全表,使用的是表锁;通过锁住索引实现行及锁。
-
如果select …for update没走索引,就会锁表,innodb内部是全表根据主键索引逐行扫描,逐行加锁,最后统一释放。
![](https://img-blog.csdnimg.cn/2020121800371261.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dvZ29reQ==,size_16,color_FFFFFF,t_70)
使用注意点:
没有索引或者索引失效时,InnoDB 的行锁变表锁。
4.2、两阶段锁协议
行锁是需要的时候加上的,不是不需要了就立即释放,在commit后才释放,释放时机如下:
-
1. 执行commit语句或者rollback;
-
2. 退出数据库;
-
3. 客户端断开连接;
4.3、行锁的使用优化
由于行锁的等到事务提交后才释放,不是用完就释放,所以把有冲突的抢锁操作尽量放到事务操作的最后一步进行。如何理解:
“尽量把临界资源的锁往后放”,实例如下:
实例场景:支付订单中一般流程较长,步骤较多,比如商品库存就是一临界资源,容易产生竞争,可以将扣减库存放在订单事务操作的最后一步,让事务在获取锁的等待时间尽量短,提高并发度。
5、行锁与索引的关系分析
从一条简单的SQL语句开始,分析索引对加锁的影响:
SQL: delete from Order where orderId = 33;
情况一:在RC级别下,id为主键索引,orderId为普通索引,忽略订单ID重复的情况;
表结构如下:
CREATE TABLE `Order` (
`id` int(11) NOT NULL,
`orderId` bigint NOT NULL,
`goods_name` varchar(45) DEFAULT NULL COMMENT '商品名称',
PRIMARY KEY (`id`),
KEY `orderId` (`orderId`)
) ENGINE=InnoDB
分析:此时加行锁会锁住对应条件的二级索引及主键索引的行记录;
情况二:在RC级别下,id为主键索引,orderId为唯一索引;
表结构如下:
CREATE TABLE `Order` (
`id` int(11) NOT NULL,
`orderId` bigint NOT NULL,
`goods_name` varchar(45) DEFAULT NULL COMMENT '商品名称',
PRIMARY KEY (`id`),
UNIQUE KEY `orderId_UNIQUE` (`orderId`),
) ENGINE=InnoDB
分析:由于id是唯一索引时,where会走orderIde列的索引进行过滤,在找到orderId=33的记录后对唯一索引orderId=33的记录加X锁。
同时会回表查询主键索引orderId=33的数据,并对orderId=33的数据也加上X锁。此时依然加行锁,锁住唯一索引及主键索引对应的行记录
情况三:在RC级别下,只有id主键索引;
表结构如下:
CREATE TABLE `Order` (
`id` int(11) NOT NULL,
`orderId` bigint NOT NULL,
`goods_name` varchar(45) DEFAULT NULL COMMENT '商品名称',
PRIMARY KEY (`id`),
) ENGINE=InnoDB
分析:从上面分析的行锁的锁原理可知,因为没有索引,所以走的是全表扫描,此时没有行锁,会对主键索引上每一条记录施加X锁,会锁住整个的表数据;
思考:
为什么会对所有记录施加X锁,而不是表锁或者说符合条件的数据加X锁呢?
这是由于InnoDB的实现决定的,由于没有索引, 无法在存储引擎层过滤(执行计划里的Using Where),所以存储引擎对每一条数据加锁后返回给Sql Server进行过滤,因为Innodb的行锁是通过索引来实现的。
5.1、行锁使用的注意事项
-
(1)在不通过索引条件查询的时候,或没有索引的情况下,InnoDB只能使用表锁。
-
(2)由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
-
(3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
-
(4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。
对于在RR级别下,对数据的写操作不仅会锁住行还会锁住相邻值之间的间隙;也就是会出现
gap
锁
和
Next-key lock。
6、三种细粒度锁的实现
-
record lock-行锁:
-
gap lock-间隙锁:
-
Next-Key lock-临键锁:行锁和间隙锁共同实现的锁;
![](https://img-blog.csdnimg.cn/2020121800371267.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dvZ29reQ==,size_16,color_FFFFFF,t_70)
6.1、行锁Record lock
实现:通过索引实现,锁住一行记录,锁粒度最小,并发度高。
使用条件:等值查询;
![](https://img-blog.csdnimg.cn/20201218003711296.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dvZ29reQ==,size_16,color_FFFFFF,t_70)
6.2、使用间隙锁Gap Lock
使用条件:查询一个不存在的值,锁定就是该值左右的区间,就是间隙锁。
-
间隙锁与间隙锁之间是不冲突的,跟间隙锁冲突的是: 往间隙之间插入一条数据的这个操作 。
![](https://img-blog.csdnimg.cn/2020121800371221.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dvZ29reQ==,size_16,color_FFFFFF,t_70)
6.3、临键锁 Next-key Lock
临键锁锁住的是什么?
使用条件:范围查询,包含等值和区间
-
锁住的是下一个记录的左开右闭的区间;
-
解决了RR级别下的数据幻读;
![](https://img-blog.csdnimg.cn/2020121800371222.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dvZ29reQ==,size_16,color_FFFFFF,t_70)
7、小结
Mysql的行锁和表锁区别:
-
表级锁: 锁住整张表。 开销小,加锁快;不会出现死锁;锁粒度最大,发生锁冲突的概率最高,并发度低;
-
行级锁: 锁住一行数据。开销大,加锁慢;容易出现死锁;锁粒度最小,发生锁冲突的概率最低,并发度高;
行锁的实现:
-
如果有索引,那么会先扫描索引文件,查询到主键id,通过索引锁定行记录实现行锁;
-
如果没有索引,就会锁住全表的数据;
行锁的出现大大提高了数据库的并发度,当然实现相对复杂,并且产生死锁的概率也提升了,使用时需要注意。
OK---盛年不再来,一日难再晨。
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。
##参考资料,
《Innodb存储引擎》