数据库的事务和并发问题

数据库事务与隔离级别
本文详细解析了数据库事务的概念,包括事务的四大特性:原子性、一致性、隔离性和持久性,以及事务在并发控制中可能遇到的问题,如脏读、不可重复读、幻读和丢失更新。同时,介绍了数据库如何通过不同的隔离级别来解决这些问题。

数据库事务

事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么全部执行,要么全部都不执行。比如,银行转账,从一个账号扣钱,然后另一个账号余额增加,这两个操作要么都执行,要么都不执行。这两个操作组合在一起就是事务。

数据库事务有严格的定义,它必须同时满足4个特性:

  1. 原子性,Atomic
  2. 一致性,Consistency
  3. 隔离性, Isolation
  4. 持久性,Durabiliy

简称ACDI。下面是对每一个特性的说明:

  • 原子性:表示组成一个事务的多个数据库操作是一个密不可分的原子单元,只有所有的操作执行成功,整个事务才提交。事务中的任何一个数据库操作失败,已经执行的任何操作都必须撤销(回滚),让数据库恢复到事务提交之前的状态。
  • 一致性:数据库总是从一个一致性状态装换到另一个一致性状态。一致性状态的含义是数据库中的数据应该满足数据库约束。
  • 隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰。但是也并非要做到完全无干扰。数据库规定了多个隔离级别,不同的隔离级别的干扰程度是不同,隔离级别越高,数据一致性越好,但并发性越弱。
  • 持久性:一旦数据库提交之后,事务中的所有操作都必须被持久化都数据库中。即使在提交事务后,数据库重启时,也必须保证能够通过某种机制恢复数据。

在这些事务的特征中,数据”一致性“是最终目标,其他特性都是为达到这个目标而采取的措施。

数据库并发的问题

一个数据库可能会有多个客户端同时访问,数据库中相同的数据就有可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种问题,破坏数据的完整性,这些问题可以分为5中,两类:

  1. 数据读取的问题:
    • 脏读
    • 不可重复读
    • 幻想读
  2. 数据更新问题
    • 第一类丢失更新
    • 第二类丢失更新
1. 脏读(direct read)

A事务读取B事务尚未提交更改的数据,并在这个数据的基础上进行操作。如果恰巧B事务回滚,那么A事务读取到的数据是不被承认的。通过一个取款事务和转账事务来说明这个问题。

时间转账事务A取款事务B
T1开始事务
T2开始事务查询账户余额1000元
T3取出500元,把余额改为500元
T4查询余额500元(脏读)
T5撤销事务,余额恢复为1000元
T6汇入100元,把余额改为600元
T8提交事务

在这个场景中转账事务A读取到取款事务B中的未提交的数据,导致脏读。

2. 不可重复读(unrepeatable read)

不可重复读是指A事务读取B事务已经提交更改的数据。假设A在取款事务的过程中,B往该账户转账100元,A两次读取账户的余额不一致。

时间取款事务A转账事务B
T1开始事务
T2开始事务
T3查询账户余额为1000元
T4查询账户余额1000元
T5取出100元,把余额改为900元
T6查询事务
T7查询账户余额为900元

在同一事务中,T4和T7时间点读取的账户余额不一致。

3. 幻想读(phantom read)

A事务读取B事务提交的新增数据,这时A事务将出现幻想读现象。幻想读一般发生在计算统计数据的事务中。

举个例子,比如在银行系统的同一个事务中有两次统计存款用户的总金额,在两次统计中刚好新增了一个存款,这时,两次统计的结构将会不一致。

时间统计金额事务A转账事务B
T1开始事务
T2开始事务
T3统计总存款为1000元
T4新增一个存款账户,存款100元
55提交事务
T6再次统计总存款数为10100元(幻想读)

如果新增的数据刚好满足事务的查询条件,那么这个新数据就会出现事务的视野中,因而产生了两次结构不一致。

幻想读和不可重复读这两个概念比较容易混淆
,幻想读是指读到了其他已经提交的事务的新增数据,而不可重复读是指读到了已经提交的更改数据(更改或者删除)。

为了避免这两种情况,采取的对策是不同的:防止读到更新的数据,只需要对操作的数据添加行级锁,阻止操作中的数据发生改变;而防止读到新增的数据,则往往需要添加表级锁——将整张表加锁,防止新增数据。

4. 第一类丢失更新

第一类丢失更新是一个事务撤销时把另一个事务的数据覆盖了。下面通过一个转账的例子来看一下这个问题。

时间取款事务A转账事务B
T1开始事务
T2开始事务
T3查询余额为1000元
T4查询余额为1000元
T5汇入100元,把余额修改为1100元
T6提交事务
T7取出100元,把余额修改为900元
T8撤销修改
T9把余额恢复为1000元(丢失更新)
5. 第二类丢失更新

一个事务覆盖另一个事务已经提交的数据。造成另一个事务所做的操作丢失。

时间取款事务A转账事务B
T1开始事务
T2开始事务
T3查询账户余额为1000元
T4查询账户余额为1000元
T5取出100元,把余额修改为900元
T6提交事务
T7汇入100元
T8提交事务
T9把余额修改为1100元(丢失更新)

总结:第一类为撤销时覆盖,第二类为提交时覆盖。

数据库锁机制

数据库的并发会引起很多问题,当然有些问题还可以容忍,但是有的问题却是致命的。并发问题一般都会用锁解决,在数据库中也是用锁解决的,但是不同的数据库对于锁的实现是不同的,但基本的原理是相同。

按锁定的对象可以分为:

  • 表锁定:对于整张表锁定
  • 行锁定:对于表中的特定行锁定

从并发的数据关系中又可以分为

  • 独占锁:共享锁会防止独占锁,但允许其他共享锁的访问。
  • 共享锁:独占锁独自占领表或行,防止其他共享锁的访问,当然也访问其他独占锁。

在数据更新的时候,数据库必须在进行更改的行上施加行独占锁,也就是说INSERT,UPDATE,DELETE等语句都会隐式采用必要的行锁定。

事务的隔离级别

尽管数据库为用户提供了锁的DML操作方式,但是直接使用还是很麻烦的,因此数据库为用户提供了自动锁的机制。也就是隔离级别,只要用户指定的回话的隔离级别,数据库就会分析SQL语句,然后进行合适的加锁,当数据锁的数据太多的时候,自动进行锁升级来提高系统的,性能,这一过程对用户是透明的(不可见)的。

SQL标准定义了4个事务级别,每一个级别都规定了一个事务中所做的修改,哪些在事务中是可见的,哪些是不可见的。较低的隔离通常可以执行更高的并发,系统开销也更低。

下面的是四中数据库事务的介绍:

  1. READ UNCOMMITED(未提交读)
    事务中的修改,即使没有提交对其它事务都是可见的。事务可以读取未提交的数据,这也被称为脏读。一般很少使用。
  2. READ COMMITED(提交读)
    大多数的数据库的默认隔离级别都是READ COMMITED。READ_COMMITED从一个事务开始时,只能”看见“已经提交的修改。也就是说:一个事务从开始到提交前,所做的任何修改对其他事务是不可见的。这个级别有时候也叫做不可重复读,因为两次执行查询可能会得到不同的结果。
  3. REPEATABLE READ(可重复读)
    REPEATABLE READ解决了脏读的问题,该级别保证在同一个事务中多次读取同样的记录的结果是一致的。但是这个级别还是没有解决另一个问题:幻读。
  4. SERIALIZABLE(可串行化)
    SERIALIZABLE是最高的隔离级别。它通过事务串执行,避免了前面所说的幻读问题。简单来说SERIALIZABLE会为每一行数据都加锁,所以会导致大量的锁超时和竞争。实际中很少使用这个隔离级别。

下表为事务隔离级别对并发问题的解决情况:

隔离级别脏读不可重复读幻想读第一类丢失更新第二类丢失更新
READ UNCOMMITED允许允许允许不允许允许
READ COMMITED不允许允许允许不允许允许
REPEATABLE READ不允许不允许允许不允许不允许
SERIALIZABLE不允许不允许不允许不允许不允许

其中READ UNCOMMITED并发性和吞吐量最好,SERIALIZABLE的最差,所以事务的隔离级别和数据库的并发行是对立的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值