这一段时间一直在学习关系型数据库,准备写一个小专题来总结一下这一段时间的学习结果。
一. 锁的分类
网上很多博客都是直接说了一连串的锁,什么悲观锁乐观锁,什么读写锁,什么排他锁共享锁。说的不仅语焉不详,而且分类紊乱,希望看到这篇文章能帮助你理清思路。
1.1 从锁的实现方式划分:乐观锁和悲观锁
从锁的实现方式来看,锁可以分为悲观锁和乐观锁。
- 乐观锁:以乐观的态度去预估并发操作中的数据冲突,不使用数据库提供的锁机制,而是通过数据库用户(程序员)添加某些字段来实现锁的机制,乐观锁不会真实的给数据上锁,所以开销较小。
- 悲观锁:悲观锁指的就是数据库管理系统提供的锁,之所以叫悲观锁,是因为这种锁无论是否有并发冲突,都会给数据上锁,因此开销相对来说较大。
乐观锁的一种实现方式:
使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是不是该数据的最新的版本号。
1.查询出商品信息
select (status,status,version) from t_goods where id=#{id}
2.根据商品信息生成订单
3.修改商品status为2
update t_goods set status=2,version=version+1
where id=#{id} and version=#{version};
可以看出,如果版本号在事物A进行中,发生了改变(事物B更新了数据),那么事物A就不能成功的修改数据,相当于加了一把锁。
但是乐观锁不适合高并发的情况,因为有可能几个事物同时读取了同一个版本号,然后同时进行了修改,这样就会出现数据不一致问题。所以数据库提供的悲观锁其实是必要的
1.2 从锁的加锁机制:共享锁和排他锁
从加锁的机制上,悲观锁分为共享锁和排他锁,共享锁又叫读锁,排他锁又叫写锁
共享锁和排他锁时数据库管理系统提供的数据锁,因此属于据悲观锁。
- 共享锁:共享锁常用于读取数据时,某用户(事物)对数据加共享锁以后,其他用户(事物)只能对该数据加共享锁,无法加排他锁。
- 排他锁:排他锁常用于写数据时,某用户(事物)对数据加排他锁以后,其他用户(事物)不能再对该数据加任何锁,只能等到解锁以后才可以继续加锁。
需要说明的是,在数据库管理系统中,所有的写操作都需要加排他锁,而读操作可以选择加共享锁,排他锁,也可以不加锁。因此一个加了共享锁的读操作,会导致其他事物无法修改这个读的数据。(这也就是serializable级别的实现方式)
在MylSQL中,如果SELECT操作加共享锁:
SELECT * FROM table_name LOCK IN SHARE MOED;
如果SELECT操作加排他锁:
SELECT * FROM table_name FOR UPDATE;
1.3 从锁的粒度上来说:行级锁和表级锁
首先要说明的是,无论是行级锁还是表级锁,都是数据库管理系统实现悲观锁的一种方式,也就是说共享锁和排他锁可以是行级也可以是表级。
- 表级锁:加锁时整个表都锁定,整个表的内容都无法被修改,直到解锁为止。这种锁因为锁的粒度较大,所以发生冲突的可能性较高,并发性低,但同时出现死锁的概率也相对较低。
- 行级锁:加锁时只有某行数据被锁定,而表内其他行不加锁,可以被修改和访问。这种锁发生冲突的概率较低,并发性较高,但是出现死锁的概率相对来说较高。
Mysql有些数据库引擎还提供了页级锁,但这些都是DBMS特异性的功能,在此就不表述。
二. 事物隔离等级和锁的关系
从理论上来说,事物的隔离等级都是通过数据库锁来实现的,但是又因为了MVCC机制出现,现代的数据库管理系统对于事物隔离等级的实现并不同。因此在这里只简单说明一下他们的关系。
1.1 写操作
对于数据库管理系统(MYSQL,SQLserver和Oracle)来说,所有的写操作(INSERT,UPDATE,DELETE),都是要加排他锁的,根据排他锁的性质我们可以得知,排他锁未解锁之前,不能再加排他锁,这样就保证了并发情况下的数据一致性。
因此在所有事物隔离等级中,写操作未提交之前,其他事物对于该数据的写操作都无法进行,因为该数据已经锁定。
要多说一点的说,数据库管理系统对于写操作时锁的粒度是有不同的,以Mysql来说,对于InnoDB引擎,对于索引的数据进行写操作,会使用行级锁,但是如果这个数据没有索引,还是会对全表加锁。
1.2 读操作
根据前面我所说的,事物其实主要针对数据库的写操作,而Mysql中事物写操作加锁的情况,仅仅出现在serilizable
等级下,这种事物隔离等级下,读操作加共享锁写操作加排他锁。
其他隔离等级下的SELECT
操作是不会加锁的,因为Mysql有一种读快照(snapshot read)的机制,也就是说其实普通的读操作不会真正读取到数据库中的内容,而是读取了一个版本的快照,只有加锁读(共享锁还有排他锁都可以),才是读取数据库当前存储的内容。