数据库事务的四大特征以及隔离级别初探

数据库的相关知识是每个开发人员必备的技能,了解和学习常见的关系型数据库是非常重要的,今天就初略地谈一谈关系型数据库中事务的四大特性,以及事务的隔离级别,最后再说一说隔离级别的实现方式。

事务的四大特性

相信每个人都知道,所谓的四大特性就是我们经常说到的ACID,下面来做详细的介绍:

  • 原子性(Atomicity):事务是数据库工作的逻辑单位,一个事务可能包含了多个对数据库的操作,这些操作,要么都做,要么都不做。

  • 一致性(Consistency):事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。其实关于一致性的理解有些模糊,网上的解释也是五花八门。

  • 隔离性(Isolation):一个事务的执行不能被其它事务所干扰。一个事务内部的操作及操作的数据对其它事务来说应该是隔离的,不能互相干扰。而关于隔离性其实是有多个级别的,不同的级别对事务之前的可见性都有相应 的规定。

  • 持久性(Durability):一个事务提交后,对数据库的更改是永久的,后期的事务提交及其它操作都不能影响其结果。

事务的隔离级别

在大学课程中就学过,事务的隔离级别分为四个等级:

  • 未提交读

所谓未提交读,指的就是一个事务能够看到另一个未提交事务对数据库的更改。

举个栗子🌰:

事务A对数据库的某一条记录进行更改,将某条记录的值从100改为200,但是还未提交事务,此时事务B要读取这条记录,那么事务B读取出来的值是200,而不是100,此时,如果事务A进行了回滚操作,将值回滚为100,此时事务B读取的数据就是不正确的,这就造成了所谓的脏读。所以如果数据库采用此隔离级别,那么有可能存在脏读问题,一般的数据库系统都没有采用该等级。

  • 提交读

和未提交读相对应,提交读是指一个事务只能看到到另一个已经提交事务对数据库的更改。

举个栗子🌰:

还是上面的问题,因为事务A还未提交,所以事务B读取的时候是100,这下脏读的问题解决了,但是后面事务A没有回滚,而是正常提交,将值更新为200,若此时,事务B再次读取该记录,会读取出200,和第一次读取的值不一致,这就是所谓的不可重复读,注意,两次读取是在同一个事务B中的。所以如果数据库采用此隔离级别,那么有可能存在该问题,不过大多数的数据库系统默认都是该隔离等级。

  • 可重复读

可重复读是指在同一个事务中,读取出来的结果总是一致的。它是MySQL InnoDB引擎默认的事务隔离等级。

举个栗子🌰:

依然是上面的问题,事务B你再怎么读,读取出来的那条记录的值总是100啊,这下好了,脏读和不可重复读的问题都解决了,但是啊,事务B又执行了一条sql:select count(*) from XXX where id>2,结果数据是20条,在事务B提交之前,又执行了一次sql,结果居然是22条,这下mysql就懵了,喔日,怎么两次统计的结果不一致呢,最后查啊查,发现啊,是事务C和事务D插入了两条记录,这就是所谓的幻读,读取出了幽灵行,不知道是哪里多出来的记录数。

值得注意的是:幻读和不可重复读之间的区别是:不可重复读侧重的是同一条记录读取出了不同的值,而幻读侧重于查询某一个范围内的数据,发现出现不知哪里来的幽灵行数据。
  • 串行化

串行化就厉害了,什么问题都没有,原理就是不管你是什么事务,都给我排好队,一个一个来执行。

举个栗子🌰:

呃呃~这个就不用举🌰了,大家都懂。我们为啥搞这么多级别啊,不就是为了解决事务并发运行的问题嘛,你都一个一个运行了,还能有啥问题,除非是数据库崩了,还有就是吧,效率太低了,不能充分发挥mysql强大的性能。

存在的问题

在上面介绍四种隔离等级的时候,我们了解了不同的隔离等级可能会带来的一些问题,包括:脏读,不可重复读,幻读,其实除了这三种,还存在一类问题,统称为:更新丢失(lost update),下面来详细了解下:

丢失更新

丢失更新分为两种情况,分别是回滚覆盖和提交覆盖。

  • 回滚覆盖

举个栗子🌰:

现有两个事务:事务A读取数据库某条记录的值,读取出来是100,并将值更新为10,还未提交事务。事务B将该条记录的同一个列值更新为200,然后提交事务,而此时事务A却因为某种原因发生了回滚,将列值直接回滚为100,然后提交。难以想象,一个事务的回滚竟然影响了另一个正常提交的事务,这是非常可怕的。这也被称为第一类更新丢失问题

  • 提交覆盖

依然举个栗子🌰:

现在有两个事务:事务A和事务B同时读取数据库的某条记录,然后事务A将某个列值-10,事务B将该列值+20,但是事务B先提交,事务A后提交,事务A提交的更新直接将事务A提交的覆盖。这被称为第二类更新丢失问题

 ⚠️ 解决更新丢失问题的方法就是:当一个事务中包含对数据库的更新时,在事务开始时必须加X锁,而且是持续的X锁,才能保证其它事务不会对数据库进行读取和更新

隔离等级的实现方式

我们上面讲了四种隔离等级,以及不同的等级可能会存在的问题,下面我们来探究下如何才能实现这四种隔离等级,通过什么方式实现。

加锁🔒实现四种隔离等级

锁只是实现隔离级别的方式之一,也是最普遍的方式,除了锁,其余的实现方式还有 时间戳,多版本控制等等,这些方式称做无锁的并发控制

锁粒度

  • 行级锁

最大程度地支持了并发,在mysql的InnoDB存储引擎中实现了行级锁。

  • 表锁

最基本的锁策略,用户在对某张表进行写入操作(增加,删除,修改)时先获得写锁,从而锁定整张表,阻塞所有对这张表的操作。没有写锁时就获得读锁,读锁是不互相阻塞的。

共享锁和排他锁

  • 共享锁

Share lock,也被称为读锁,S锁,一般在读取数据的时候加共享锁,共享锁会阻塞X锁的获取,不会阻塞S锁。

  • 排他锁

Exclusive lock,也被称为写锁,X锁,一般在更新数据的时候加排他锁,排他锁会阻塞其它的X锁和S锁。

实现方式

  • 未提交读

写操作加持续的X锁,读操作不加锁。

⚠️ 有些文章在解释时说未提交读不需要加任何锁,但实际是不行的,因为有一类问题在任何级别都不允许,即丢失更新问题,细说就是回滚覆盖,不允许两个事务同时对同一条记录更新。
⚠️ 所谓持续的锁,是指在等整个事务都提交后,再释放锁。
临时的锁,是指不必等到整个事务都提交后再释放,执行完某条sql后,立即释放锁。
  • 提交读

写操作加持续X锁,读操作加临时S锁。不会出现脏读。

⚠️ 因为读操作需要获取S锁,在写事务未释放X锁之前,获取S锁是被阻塞的,就不能读取数据。保证了读操作只能读取到其它已提交事务进行的更新,即避免了脏读。
  • 可重复读

写操作加持续X锁,读操作加持续S锁。

⚠️ 在提交读中,获取S锁是临时的,读完后马上释放,其他事务就可以获取X锁,就可以写,若是再次读取,可能读到不一样的内容,这就是不可重复读。为了让事务可以重复读,加在读操作的S锁变成了持续S锁,也就是直到事务结束时才释放该锁,这可以保证整个事务过程中,其他事务无法进行写操作,所以每次读出来的记录是一样的。
  • 串行化

为了解决幻读问题,行级锁做不到,需使用表级锁。

非常初略地了解了下事务的相关知识,待后期继续优化补充。

已有 1 条评论
  1. 牛顿的苹果:apple:

    占个楼哈哈

发表新评论