数据库四种隔离级别
事务的特性
- 原子性(Atomicity):原子性是指一个事务中的操作,要么全部成功,要么全部失败,如果失败,就回滚到事务开始前的状态。
- 一致性(Consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。那转账举栗子,A账户和B账户之间相互转账,无论如何操作,A、B账户的总金额都必须是不变的。
- 隔离性(Isolation):隔离性是当多个用户 并发的 访问数据库时,如果操作同一张表,数据库则为每一个用户都开启一个事务,且事务之间互不干扰,也就是说事务之间的并发是隔离的。再举个栗子,现有两个并发的事务T1和T2,T1要么在T2开始前执行,要么在T2结束后执行,如果T1先执行,那T2就在T1结束后在执行。关于数据的隔离性级别,将在后文讲到。
- 持久性(Durability):持久性就是指如果事务一旦被提交,数据库中数据的改变就是永久性的,即使断电或者宕机的情况下,也不会丢失提交的事务操作。
如果不考虑隔离性,会发生什么事呢?
- 脏读
脏读指事务A读取到了事务B更新了但是未提交的数据,然后事务B由于某种错误发生回滚,那么事务A读取到的就是脏数据。
具体的说,一个数据原本是干净的,但是事务B将它进行修改使得其不再干净,此时事务A读取到事务B修改后的数据,也就是脏数据,称为脏读,后来事务B由于良心发现又将数据回滚为最初的样子,而事务A不知道事务B进行了回滚操作,最终事务A读取到的是脏数据,称为脏读。
时间顺序 | 转账事务 | 取款事务 |
---|---|---|
1 | 开始事务 | |
2 | 开始事务 | |
3 | 查询账户余额为2000元 | |
4 | 取款1000元,余额被更改为1000元 | |
5 | 查询账户余额为1000元 | |
6 | 取款操作发生未知错误,事务回滚,余额变更为2000元 | |
7 | 转入2000元,余额被更改为3000元(此时发生了脏读) | |
8 | 提交事务 | |
备注 |
- 不可重复读
不可重复读指在数据库访问时,一个事务在前后两次相同的访问中却读到了不同的数据内容。
比如说事务A的执行周期较长,事务A在第一次读取某个数据时,此数据的值为100,读取完成后,事务A又去执行其他的事情,在这个过程中,事务B将这个数据的值修改为200,然后事务A做完其他的事情后,又来读取这个数据的值,发现这个值和第一次所读取的值不相同,这种现象称为不可重复读。
时间顺序 | 转账事务 | 取款事务 |
---|---|---|
1 | 开始事务 | |
2 | 第一次查询,小明的年龄为20岁 | |
3 | 开始事务 | |
4 | 其他操作 | |
5 | 更改小明年龄为30岁 | |
6 | 提交事务 | |
7 | 第二次查询,小明的年龄为30岁 | |
备注 | 按照正确逻辑,事务A前后两次读取到的数据应该有一致性 |
- 幻读
幻读指的是事务A在查询完记录总数后,事务B执行了新增数据的操作,事务A再次查询记录总数,发现两次查询的结果不一致,平白无故的多了几条记录,这种现象称为幻读。
时间顺序 | 转账事务 | 取款事务 |
---|---|---|
1 | 开始事务 | |
2 | 第一次查询,数据总量为100条 | |
3 | 开始事务 | |
4 | 其他操作 | |
5 | 新增100条数据 | |
6 | 提交事务 | |
7 | 第二次查询,数据总量为200条 | |
备注 |
不可重复度和幻读的区别
幻读和不可重复读的本质是一样的,两者都表现为两次读取的结果不一致。但是不可重复读指的是两次读取同一条记录的值不同,而幻读指的是两次读取的记录数量不同。
不可重复读重点在于update和delete,而幻读的重点在于insert。
如果使用锁机制来实现这两种隔离级别,在不可重复读中,事务A第一次读取到数据后,就将这些数据加行锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住新增的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以新增数据并提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。
以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。
所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。
表锁
我们在编辑表,或者执行修改表的事情了语句的时候,一般都会给表加上表锁,可以避免一些不同步的事情出现,表锁分为两种,一种是读锁,一种是写锁。
加读锁(共享锁):
- 我们加读锁的这个进程可以读加读锁的表,但是不能读其他的表。
- 加读锁的这个进程不能update加读锁的表。
- 其他进程可以读加读锁的表(因为是共享锁),也可以读其他表
- 其他进程update加读锁的表会一直处于等待锁的状态,直到锁被释放后才会update成功。
加写锁(独占锁):
- 加锁进程可以对加锁的表做任何操作(CURD)。
- 其他进程则不能查询加锁的表,需等待锁释放
总结:
读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞。(特别注意进程)
行锁
-
当我们对一行进行更新但是不提交的时候,其他进程也对该行进行更新则需要进行等待,这就是行锁。
-
如果我们对一行进行更新,其他进程更新别的行是不会受影响的
四种隔离级别解决了上述问题
1.读未提交(Read uncommitted)
这种事务隔离级别下,select语句不加锁。
此时,可能读取到不一致的数据,即“读脏 ”。这是并发最高,一致性最差的隔离级别。
2.读已提交(Read committed)
可避免 脏读 的发生。
在互联网大数据量,高并发量的场景下,几乎 不会使用 上述两种隔离级别。
3.可重复读(Repeatable read)
MySql默认隔离级别。
可避免 脏读 、不可重复读 的发生。
4.串行化(Serializable )
可避免 脏读、不可重复读、幻读 的发生。