文章目录
- 前言
- 零、数据库引擎
- 一、封锁粒度
- 二、行锁
- 三、表锁
- 四、数据库中的属性锁
- 4.2. 意向锁
- 五、乐观锁和悲观锁
- 总结
前言
之前我们提到了数据库的隔离性可能会出现的若干问题,以及数据库为了解决这些问题而提出来的若干种隔离级别。实际上,数据库底层实现这些隔离级别的原理本质上就是通过各种封锁。今天我们在这里总结一下两种封锁粒度。
零、数据库引擎
其实mysql中的引擎有很多种类,其中InnoDB和MyISAM引擎最常用
在mysql5.5版本前默认使用MyISAM引擎,之后使用InnoDB引擎
MyISAM 操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高。当然同时它也不会存在死锁问题。
而 InnoDB 与 MyISAM 的最大不同有两点:一是 InnoDB 支持事务;二是 InnoDB 采用了行级锁。
在 Mysql 中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql 语句操作了主键索引,Mysql 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。
一、封锁粒度
mysql中提供了两种封锁粒度:行级锁和表级锁。
我们在使用的过程中应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能性就越小,系统的并发程度就越好。
但是加锁需要消耗资源,锁的各种操作(包括获取锁,释放锁以及检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销越大。 在选择封锁粒度的时候需要在锁开销和并发程度之间做出一个权衡。
两者之间的区别如下:
-
表级锁: 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。但触发锁冲突的概率最高,并发度最低
-
行级锁: 粒度最小 的一种锁,只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
其实除了可以分为表级锁和行级锁,锁还可以被划分为其他类型,具体如下图所示:
二、行锁
行锁就是一锁锁一行或者多行,mysql的行锁是基于索引家在的,所以行锁是要加载在索引响应的行上(比如当你更新表中的索引字段的时候会自动响应行锁)。当我们使用行锁锁定了数据库中某个表的某些记录的时候,当其他事务访问数据库中的该表,被锁定的记录不能被访问,其他的记录都可以访问到。
行锁的特点是锁冲突概率低,并发性高,但是会出现死锁的情况。
三、表锁
顾名思义,表锁就是一锁锁一整张表,在表被锁定的期间,其他事务不能对该表进行任何操作,必须等当前表的表锁被释放后才能进行操作。表锁响应的是非索引字段(比如你在更新表中的非索引字段的时候会自动调用表锁),即全表扫描,全表扫描时锁定整张表。
由于表锁每次都是锁一整张表,所以表锁的锁冲突几率特别高,表锁大大降低了出现死锁的情况
四、数据库中的属性锁
属性锁可以分为两类,共享锁和排他锁。
-
排他锁(互斥锁),简写为X锁,又称之为写锁,简称X锁,当事务对数据加上写锁后,其他事务既不能对该数据添加读写,也不能对该数据添加写锁,写锁与其他锁都是互斥的。只有当前数据写锁被释放后,其他事务才能对其添加写锁或者是读锁。写锁主要是为了解决在修改数据时,不允许其他事务对当前数据进行修改和读取操作,从而可以有效避免”脏读”问题和“丢失修改”问题的产生。
-
共享锁,简写为S锁,又称为读锁,简称S锁,当事务对数据加上读锁后,其他事务只能对该数据加读锁,不能做任何修改操作,也就是不能添加写锁。只有当数据上的读锁被释放后,其他事务才能对其添加写锁。共享锁主要是为了支持并发的读取数据而出现的,读取数据时,不允许其他事务对当前数据进行修改操作,从而避免”不可重读”的问题的出现。
数据库的增删改操作默认都会加排他锁,而查询不会加任何锁
当然,我们也可以手动使用共享锁和排他锁,如下列代码所示:
//共享锁
select * from 表名 lock in share mode
//排他锁
select * from 表名 for update
锁的兼容关系如下:
4.2. 意向锁
使用意向锁可以更容易地支持多粒度封锁。
在存在行级锁和表级锁的情况下,事务T如果想要对表A加上锁,就需要先检测是否有其他事务对表A或者表A中的任意一行加锁,如果一行一行去检查就显得非常沙雕且耗时,且效率极低,这个时候我们只需要检测意向锁是否被占用就行。
意向锁在原来的X/S锁的基础上引入了IX/IS,IX/IS都是表锁。有以下两个规定:
- 一个事务在获得某一个数据行对象的S锁之前,必须先获得表的IS锁或者更强的锁
- 一个事务在获得某一个数据行对象的X锁之前,必须首先获得表的IX锁。
通过引入意向锁,事务T想要对表A加X锁,只需要先检验是否有其他事务对表A加上了X/IX/S/IS锁,如果有就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。
各种锁的兼容关系如下:
五、乐观锁和悲观锁
-
乐观锁和悲观锁都是针对select而言的
比如在商品抢购中,用户购买后库存需要减1,而很多用户同时购买时,读出来的库存数量一样,然后多个用户同时用该库存去减1。 -
这种做法必然会出现很大的漏洞,如果向在淘宝,京东出现这种情况,你就可以打包回家种地了
-
这种情况如何解决呢,其实可以使用悲观锁进行解决,说白了也就是排他锁。用户进来查库存的时候,就加上排他锁,等他所有操作完成后,再释放排他锁,让其他人进来
不让用户等待,就可以使用乐观锁方式解决,乐观锁一般靠表的设计和时间戳来实现
一般是在表中添加version或者timestamp时间戳字段 -
这样就会保证如果更新失败,就表示有其他程序更新了数据库,就可以通过重试解决
update table set num=num-1 where id=10 and version=12