对数据库事务、隔离级别、锁、封锁协议的理解及其关系的理解

前言

写篇文章来巩固自己学习的知识。通过写文章,本着一颗不误导别人、不混过去的心,找出那些自己看似懂了的问题。这篇文章主要讲的是事务、隔离级别、锁、封锁协议之间的关联。我觉得最重要的是要将这些知识关联起来。而不是单独的去理解。
在这里提一个问题。在了解一级封锁协议的定义后,你能解释为什么一级封锁协议能解决更新丢失的问题吗。如果不能,那么这篇文章也算是能给你带来一点收获了。

事务的四大特性

1.原子性
2.一致性
3.隔离性
4.持久性

事务在并发下产生的问题

并发情况下,事务会出现什么问题呢?

1.更新丢失
2.脏读
3.不可重复读
4.幻读

【事务】和【并发】的关系:单线程下事务不会出问题,因为是单个线程在操作数据库,事务操作成功则记录到磁盘中。失败则进行回滚。但是并发情况下,有多个事务在操作数据库,这样,就有可能产生一些问题。

举例来说。
更新丢失:a事务中更新一条记录,却被b事务的更新操作给覆盖了。也就是说,a事务的操作没有生效。
脏读:b事务中执行了修改操作:将name从lee改成了tom,但是还没提交,a事务中读到到的数据是tom,当事务b回滚,name变回了lee。这样就导致了a事务中读到的tom是不存在的。
不可重复读:事务a中读取了name字段,其值为lee。在第二次读取name字段的过程中,事务b将name字段的值改成了jack,事务a第二次读到的是jack,发现与第一次读到的不一样,即两次读到的数据不一样
幻读:假设表(字段有id主键和value)中有三条记录,分别为(1,a),(2,b),(3,c)。a事务读取全部发现这三条,b事务新增一条记录(4,d),然后提交事务。然后a事务再查询,发现还是这三条(没能读取到最新那条),于是新增一条(4,d)。结果报错了,说主键重复了。可是事务a中查询到的记录明明没有id为4的记录。这就是幻读。

事务的四个隔离级别

mysql的隔离级别有四种:

序号 英文名 中文名 会产生的问题
1 read_uncommited 读未提交(可读未提交的) 会产生脏读、不可重复读、幻读问题
2 read_commited 读已提交(提交了才能读取到) 会产生不可重复读、幻读问题
3 repeatable_read 可重复读 会产生幻读问题
4 serilizable 序列化读 不会产生问题

上面的表述让人很难理解,或者说是有歧义的。不应该说哪种隔离级别会产生哪种问题,而是应该说哪种隔离级别没能解决哪种问题。

事务在并发情况下,会出现这些问题:丢失更新、脏读、不可重复读、幻读。而数据库的隔离级别,是用来解决多个事务操作同一数据库对象时出现的冲突问题。

事务在并发下会出现问题,而使用不同的事务隔离级别,可以不同程度的解决这些问题。所以,应该这样写才对:

序号 英文名 中文名 没能解决的问题
1 read_uncommited 读未提交(可读未提交的) 没能解决事务并发操作的 脏读、不可重复读、幻读问题
2 read_commited 读已提交(提交了才能读取到) 没能解决事务并发操作的不可重复读、幻读问题
3 repeatable_read 可重复读 没能解决事务并发操作的幻读问题
4 serilizable 序列化读 能解决事务并发操作的所有问题

【隔离级别】与【并发下事务的问题】的关系:通过设置隔离级别,就相应的解决这些并发情况下带来的问题。

数据库中的锁

1.共享锁(又称为读锁和S锁)
2.排它锁(又称为写锁和X锁)

排他锁:若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁为止。
共享锁:若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁为止。

【事务隔离】与【锁】的关系:事务隔离主要就是对事务的读写之间进行隔离,通过什么来隔离?通过锁来实现隔离

【事务隔离级别】与【锁】的关系:通过对事务的读写操作加锁情况的不同,划分出不同的事务隔离级别

为什么repeatable_read能解决更新丢失、脏读、不可重复读的问题,而read_commited只能解决更新丢失、脏读的问题,而read_uncommited只能解决更新丢失的问题?

因为它们在事务读写操作上所用的锁不同。要想明白,不同的隔离级别就是对事务的读写操作加锁情况的不同

【锁】与【事务在并发下的问题】的关系:通过对事务的读写操作加锁,能解决事务在并发下的问题。

理解背后的原因

我觉得,要想看懂数据库事务这一块,最重要的是要搞明白上面说的几个关系。而不能每个部分单独的去理解。

前面说到的这个:

1.read_uncommited 读未提交(可读未提交的) 没能解决事务并发操作的 脏读、不可重复读、幻读问题
2.read_commited 读已提交(提交了才能读取到) 没能解决事务并发操作的 不可重复读、幻读问题
3.repeatable_read 可重复读 没能解决事务并发操作的 幻读问题
4.serilizable 序列化读 能解决事务并发操作的所有问题

这只是一个结论,只是告诉我们不同的隔离级别能解决并发下事务的哪些问题,没能解决哪些问题。

而接下来,我们要做的,是要去搞明白结论从何而来,去理解背后的原因。也就是去理解这些不同的隔离级别对事务的读写操作是怎么加锁的。去理解对事务的读写操作加锁是如何解决并发下的问题的。

理解了背后的原因,也就能理解为什么这种事务隔离级别无法解决那种并发问题,而那种事务隔离级别却可以。

一级封锁协议

一级封锁协议是指,事务T在修改数据R之前必须先对其进行加X锁,直到事务T结束才释放。(事务结束包括正常结束COMMIT和非正常结束ROLLBACK)。

一级封锁协议可以防止丢失更新的问题。事务T1想要对数据R进行修改,那么先得加X锁。这时事务T2也想对数据R进行修改,那得先给数据R上X锁。但是由于数据R已经被上了X锁,此时事务T2无法对其进行上X锁,因此只能一直等待,直到数据R上的X锁被释放,才能给它上X锁,才进行修改操作。如此,解决了丢失更新的问题。

书上说:“在一级协议中,如果仅仅是读取数据而没对其进行修改,是不需要加锁的,所以它不能保证可重复读和不读脏数据”。乍一看觉得没问题,但仔细想想感觉不太对,无法理解啊。这解释我无法接受啊。我的理解是使用了一级封锁协议,事务T1给数据R加了X锁,直到事务T1结束才会释放X锁。这样,事务T2不是只能在事务T1结束之后才能对数据R进行上锁,再进行相应操作吗???这样怎么可能会发生脏读和不可重复读的问题呢。但是这样一来,加X锁不就相当于串行化读了吗??

查阅了资料(百度知道)。才明白是书上的表述有误:
若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁为止。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A。

蓝色字的表述是有误的。

我的理解

若事务T对数据对象A加上X锁,则其他事务在T释放A上的锁之前不能再对数据A上锁。倘若,事务T2中没有上任何锁,那么它是可以直接读取和修改数据A的。因此,并不是说,事务T对数据A上X锁到释放X锁期间,其他事务就不能访问数据A。

但是,如果选择了封锁协议,情况就不一样了。譬如选择了使用一级封锁协议,那么根据一级封锁协议规定,事务T在修改数据R之前必须先对其进行加X锁,直到事务T结束才释放。也就是说,如果一个事务中有修改数据的操作,那么它必须先对该数据上X锁,才能进行修改操作。

如此,在一级封锁协议下,若事务T对数据对象A加上X锁,则其他事务在T释放A上的锁之前不能再对数据A上锁,因此也就没有机会去修改数据A了。

但是,要注意的是,一级封锁协议规定的是:“事务T在修改数据R之前必须先对其进行加X锁,直到事务T结束才释放。”它对读取数据没有作规定啊。因此,在一级封锁协议下,如果一个事务只有读取数据A的操作,那么即使数据A被上了X锁,那也没影响,照样可以读取。

也正是因为一级封锁协议下,读取操作是不受限制的。因此,一级封锁协议协议没能解决脏读和不可重复读的问题。举例来说,事务a执行了更新操作,将记录为(1,‘a’)的记录改成了(1,‘b’),这时事务b执行了查询操作,查询到这条记录是(1,‘b’)。但是事务a回滚了,该记录变回了(1,‘a’)。如此,事务b读到的(1,‘b’)就是不存在的记录,这就是脏读。而不可重复读呢,如果事务a执行了查询操作,读到的是(1,‘a’),然后这时事务b执行了更新操作,将记录为(1,‘a’)的记录改成了(1,‘b’),这时事务a进行验算操作,再查询一次,发现记录变成了(1,‘b’)。也就是说,事务a两次读取的记录是不一样,这就是不可重复读问题(重复读取结果却不一样,这就是不可重复读问题)。

可能我的表述还是没那么清晰,没关系,继续往下看,看了二级封锁协议和三级封锁协议就懂了。

二级封锁协议

二级封锁协议是指,在一级封锁协议的基础上增加事务T在读取数据R之前必须先对其加S锁,读完后即可释放S锁。

二级封锁协议是在一级封锁协议的基础上的,因此它必然能解决丢失更新的问题,除此之外还能解决脏读问题。这要为什么呢。这是因为二级封锁协议规定,在事务T在读取数据R之前必须先对其加S锁。举例来说明,还是上面脏读的例子。事务a执行了更新操作,将记录为(1,‘a’)的记录改成了(1,‘b’),这时事务b想执行查询id为1的这条记录,二级封锁协议规定了,读取之前得先对数据进行加S锁。然而此时这条数据已被上了X锁,因此事务b的查询操作必须得等到该数据的X锁被释放,才有机会执行查询操作。如此,就避开了脏读问题。而不可重复读问题还是存在的,继续上面的不可重复读的例子。如果事务a想要执行查询操作,那么得先给该数据加S锁,没问题,加锁后执行了查询操作,读到的是(1,‘a’),读完后就释放了S锁。这时事务b要执行修改操作,那么得先该记录加X锁,由于现在该记录没被加任何锁,因此事务b可以给该记录加X锁,然后执行了更新操作,将记录为(1,‘a’)的记录改成了(1,‘b’),事务b结束释放X锁。然后事务a进行验算操作,正常加上S锁然后再查询一次,发现记录变成了(1,‘b’)。那么事务a两次读到的记录是不一样的结果,因此,不可重复读的问题还是存在。

之所以不可重复读的问题会存在,就是因为,二级封锁协议规定了,在查询完之后就会释放S锁。如此一来,在事务a的两次查询中间,就让事务b的修改操作有机可趁了。

三级封锁协议

三级封锁协议是指,在以及封锁协议的基础上增加事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。

三级封锁协议是在一级封锁协议的基础上的,因此必然能解决丢失更新问题,另外,三级封锁协议规定,在增加事务T在读取数据R之前必须先对其加S锁,这和二级封锁协议一样,因此三级封锁协议也能解决脏读问题。除此之外,它还规定加上的S锁直到结束才释放。这就解决了不可重复读问题。继续以上面的例子来说。如果事务a想要执行查询操作,那么得先给该数据加S锁,没问题,加锁后执行了查询操作,读到的是(1,‘a’),读完后没有立马释放S锁。这时事务b想对该记录进行修改,那么先加R锁吧,发现,该记录已经被上了S锁,因此,没办法加R锁,因此只能一直等待,直到S锁被释放。如此就保证了事务a的两次查询之间不会插入另一个事务对同一记录的更新操作。这样一来,事务a两次读取的记录就一定会是一样的。如此,就避免了不可重复读问题。

封锁协议

这时再回过头来看一级封锁协议,以及再来理解读取记录和对记录加锁的关系,就应该能理解了吧。一个事务的读取操作是否需要等待取决于使用的封锁协议。如果使用的是一级封锁协议,那么对读取操作没有加S锁的限定,因此读取是自由的。而在二级封锁协议一级三级封锁协议中,对读取操作是要求先对所要读取的记录加S锁。因此,读取并不是自由的。

四级封装协议说我自己想的,书上没有,我想如果定义四级封装协议,就是规定:在读取和修改记录之前,要先要操作该记录加上R锁。

封锁级别越高,能解决的问题就越多。但是,即使是三级封锁协议,还是没能解决幻读问题。不过,一些情况下,些许的并发问题是能容忍的,因此我们并不要求数据库没有一丁点的并发问题。因为封锁级别越高其并发效率就越低。

单纯的封锁协议会导致活锁问题和死锁问题,活锁问题可以通过先来先服务的策略来避免。而死锁问题则复杂点,得通过死锁预防和死锁解除。这里由于自身也没什么了解就不展开了。

可以近似的把事务隔离程度和封锁协议对应起来,这里只是近似,近似,事务隔离级别是处理了死锁活锁问题的。
一级封锁协议,对应着read_uncommited,
二级封锁协议,对应着read_commited,
三级封锁协议,对应着repeatable_read,
四级封锁协议,对应着serilizable,

最后

事务这一块,要展开还会有很多东西的。封锁技术只是并发控制的技术之一,除此之外,还有时间戳、乐观控制阀、多版本并发控制即MVCC等等技术。但暂时就先这样吧。

看了许多文章,都没有解释为什么一级封锁协议可以解决丢失更新问题。而只是作为结论拿来用。那我这篇文章在这一点上,也算是有亮点吧。当然了,虽然我想到了为什么,但是自己却没想明白为什么,因此还得感谢这篇文章:百度知道

嗯,凡事多问个为什么吧。一些看似觉得懂的东西,也许问问自己为什么,就发现原来自己根本就没懂。

数据库我很渣很渣,而且我也表述、逻辑也不是很清晰。如果看官看到有哪里写的不对的,或者觉得哪里可以修改一下,希望能帮忙指出,在此谢过了。

参考文章

写这篇文章之前,先是看了许多篇别人的文章,在有了一定的了解的基础上再写的,并且在写的过程中,一遍查书,一遍上网查。

参考文章有:
MySQL的InnoDB的幻读问题
数据库事务隔离级别– 脏读、幻读、不可重复读
数据库的脏读、不可重复读、幻读以及不可重复读和幻读的区别,我觉得这一篇对幻读的解释是不正确的,我用mysql试了下,并不会出现文章所说的问题。在mysql下,事务隔离级别设为repeatable_read,因此其对应着三级封锁协议,因此,是可重复读的,因此,不会两次读取结果不一致的问题。
通过例子理解事务的4种隔离级别
数据库并发机制和事务的隔离级别详解
http://blog.csdn.net/u013349237/article/details/70201349

猜你喜欢

转载自blog.csdn.net/weixin_30531261/article/details/79479895