本篇介绍数据库的事务管理,内容皆是笔者学习《数据库系统概念》总结摘抄而来,仅作学习笔记。
事务是访问并可能更新各种数据项的一个程序执行单元。通常由高级数据操纵语言或编程语言通过JDBC或ODBC嵌入式数据库访问书写的用户程序的执行所引起。事务用形如begin transaction和end transaction语句来界定,由这两个语句之间执行的全体操作完组成。
事务具有原子性(atomicity)。如果一个事务开始执行,但由于某些原因失败,则事务对数据库造成的任何可能的修改都要撤销。无论事务本身是否失败,或者操作系统崩溃,或者计算机本身停止运行,这项要求都成立。事务是不可分割的,要么执行其全部内容,要么就根本不执行。
事务具有隔离性(isolation)。数据库系统必须才去特殊处理来确保事务正常执行而不被来自并发执行的数据库语句所干扰。
事务具有持久性(durability)。即使系统崩溃,事务的操作也必须是持久的。
事务具有一致性(consistency)。如果一个事务作为原子从一个一致的数据库状态开始独立的运行,则事务结束时数据库也必须再次是一致的。
将以上内容更简明的重新描述如下:
这些性质统称为ACID特性。
我们将采用一个由几个账户和一个访问和更新账户的事务集合构成的简单的银行应用来阐明事务的概念。事务运用以下两个操作访问数据:
设t是从账户A过户100块到账户B的事务,该事务可以定义为:
read(A);
SET A = A-50;
write(A);
read(B);
SET B = B+50;
write(B);
以此事务为例,逐个讲解ACID特性。
保证原子性的基本思路如下:对于事务要执行写操作的数据项,数据库系统在日志文件中记录其旧值。如果事务没能成功执行,数据库系统从日志中回复旧值,使得看上去事务从未执行过。这项工作是由称作恢复系统的一个数据库组件处理。
上面提到的恢复系统除了保证原子性之外还保证持久性。
事务并非总能成功的执行完成,称为事务中止(aborted)了。中止事务对数据库所做过的任何改变必须撤销以保证原子性,一旦中止事务造成的变更被撤销,我们就说事务已回滚(rolled back)。
成功完成执行的事务称为已提交(commited)。一个对数据库进行过更新的已提交事务使数据库进入一个新的一致状态。
一旦事务已提交,我们不能通过中止它来撤销其造成的影响。撤销已提交事务所造成影响的唯一方法时执行一个补偿事务(compensating transaction)。例如如果一个事务给账户A增加了20块,补偿事务是从该账户中减去20块。
我们需要更准确的定义一个事务成功完成的含义,为此我们建立了一个简单的抽象事务模型,事务必须出于以下状态之一:
只有在事务进入提交状态,我们才说事务已提交。类似的,当事务进入中止状态后,我们才说事务已中止。如果事务是提交的或者中止的,它称为已结束的(terminated)。事务相应的状态图如下:
事务从活动状态开始,当完成它的最后一条语句时就进入了部分提交状态,此刻事务已经完成执行。当数据库系统向磁盘上写入足够的信息,确保即使出现故障事务所做的更新也能在系统重启后重新创建,事务就进入了提交状态。但在部分提交状态的事务也可能由于实际输出可能临时驻留在主存中,因此一个硬件故障可能阻止其成功完成,于是事务可能必须中止。
系统判定事务不能继续正常执行后,事务就进入失败状态。这种事务必须回滚。事务就进入中止状态。此刻,系统有两种选择:
事务并发可能会导致多种问题,这些问题可以归结为两类,分别为数据读问题和数据更新问题。数据读问题包括脏读、不可重复读和幻读。数据更新问题包括第一类丢失更新和第二类丢失更新。
脏读
脏读是指事务A读到了事务B未提交的事务。
例如事务A是一个转账事务,事务B将此账户金额修改为500元但没有提交事务,此时事务A读取账户余额500元准备转账500元,此时事务B因为一些原因回滚了事务,此时账户余额为0,在事务A中就会发生明明余额有500元却转账失败的问题。此例子中事务A读到的账户余额就是一个脏数据。
不可重复读
不可重复读是指在事务A内多次读取的数据不一致,原因是在多次读取期间事务B修改了数据并提交了事务。
例如事务A读取到了账户余额为1000元,然后事务A去做其他事情,此时事务B修改账户余额为500元并提交了事务,事务A再次读取账户余额为500元。因此称为不可重复读。
幻读
幻读是指在事务A内多次读取的数据不一致,原因是在多次读取期间事务B增加或删除了数据并提交了事务。
例如事务A读取到公司员工数量为100个,然后事务A去做其他事情,此时事务B新增加了一个叫赵彦祖的员工并提交了事务,事务A再次读取到的员工数量为101个。
第一类丢失更新
第一类丢失更新是指事务A在撤销时把事务B所做的更新覆盖了。
例如事务A在转账前查询账户余额为1000元,此时事务B向账户中转了500元并提交了事务,然后事务A转账100元,发现转错了账,回滚事务又将账户金额改为1000元。事务B向账户中转的500元丢失了。
第二类丢失更新
第二类丢失更新是指事务A在提交事务时覆盖了事务B的更新。
例如事务A在转账前查询账户余额为1000元,此时事务B向账户中转了500元并提交了事务,然后事务A转账100元,提交事务将账户余额改为900元。事务B向账户中转的500元丢失了。
MySQL数据库有四种隔离级别,分别为Read uncommitted、Read committed、Repeatable read和Serializable。这四种隔离级别随着级别的递增可以解决更多的上面提到的并发问题。
Read uncommitted
读未提交,一个事务可以读取到另一个事务未提交的数据。数据库处于此级别,当一个事务写数据时,不允许另一个事务执行写操作,但其他事务可以读取此数据。这个级别防止了丢失更新问题,但无法防止读取问题。
Read committed
读已提交,一个事务不能读到另一个事务未提交的数据。数据库处于此级别,当一个事务写数据时,不允许其他事务执行读写操作。当一个事务读数据时,其他事务可以执行读写操作。这个级别防止了丢失更新和脏读,但无法防止不可重复读和幻读。
Repeatable read
可重复读取,在一个事务内多次读取一个数据都应是相同的。当一个事务写数据时,其他事务不能执行读写操作。当一个事务读数据时,其他事务不能执行写操作。这个级别防止了丢失更新、脏读和不可重复读,但无法防止幻读。
Serializable
可序化,要求事务序列化执行,不能并发执行,为隔离最高级别。由于不能并发执行,性能最低。防止了丢失更新、脏读、不可重复读和幻读(都不能并发执行了,并发引起的问题当然不会存在了)。