事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。例如,银行转账工作:从一个账号扣款并使另一个账号增款,这两个操作要么都执行,要么都不执行。所以,应该把它们看成一个事务。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性。
针对上面的描述可以看出,事务的提出主要是为了解决并发情况下保持数据一致性的问题。
事务具有以下4个基本特征。
● Atomic(原子性):事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败。
● Consistency(一致性):只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初状态。
● Isolation(隔离性):事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。
● Durability(持久性):事务结束后,事务处理的结果必须能够得到固化。
隔离级别
数据库系统有四个隔离级别。对数据库使用何种隔离级别要审慎分析,因为
1. 维护一个最高的隔离级别虽然会防止数据的出错,但是却导致了并行度的损失,以及导致死锁出现的可能性增加。
2. 然而,降低隔离级别,却会引起一些难以发现的bug。
在不同的并行控制模式下,对各隔离级别的实现是不同的。
SERIALIZABLE(序列化)
所有的数据库事务都完全隔离,就如系统中所有的事务都顺序执行一样,一个接着一个。当然,数据库可以同时执行多个事务,只要顺序执行的“错觉”可以维护(即同时执行的多个事务不会互相干扰,或者多个事务的同时执行能保证结果的正确)。
a. 基于锁的并行控制。(当执行一个带有范围的where子句的查询时,需要范围锁定。范围锁定之后,数据集中不允许update或者insert操作。)
b. 无锁并行控制。不需要锁,但是当系统检测到并行的事务违反了序列化“错觉”,它会迫使那个事务回退,所以应用程序需要重新启动那个事务。
幻读,脏读,不可重复读等问题都不会发生。
REPEATABLE READ(可重复读)
所有查询到的数据记录都不会被改变。即会在所有获取的数据上加上读锁,但是不会加上范围锁。
可能发生的问题:当执行一个范围查询时,可能会发生幻读。
READ COMMITTED(提交读)
一个查询所获取的数据可以被其他事务修改。
读锁:在所读取的数据上添加读锁,但会立即释放。
写锁:不会释放,直到事务完结。
可能发生的问题:不可重复读。
READ UNCOMMITTED(未提交读)
一个事务可以看见另一个事务所做的未提交修改。
可能发生的问题:脏读。
总结:
可以看到,对于前三种隔离级别,其实他们的不同是通过对获取的数据实行不同类型的共享机制来实现,比如添加范围锁、读锁或者立即释放读锁等来实现,但跟排他锁无关。
Serialiazble:序列化。这是最严谨的隔离级别,但是不常用。
Mysql数据库默认是Repeatable Read。
例子:转自http://blog.csdn.net/fenghu89/archive/2008/01/20/2055261.aspx
数据库的事务隔离级别(TRANSACTION ISOLATION LEVEL)是一个数据库上很基本的一个概念。为什么会有事务隔离级别,SQL Server上实现了哪些事务隔离级别?事务隔离级别的前提是一个多用户、多进程、多线程的并发系统,在这个系统中为了保证数据的一致性和完整性,我们引入了事务隔离级别这个概念,对一个单用户、单线程的应用来说则不存在这个问题。
首先,我们来看一下高并发的系统中会存在哪些问题,为了便于理解我们以张三在招商银行的账号和存款为例。
一、准备工作:
1. 创建一个银行账号Table(只是为了说明问题,不考虑表的设计范式)
CREATE TABLE dbo.BankAccount
(
BankAccountId CHAR(16) NOT NULL, -- 银行账号
UserName NVARCHAR(32) NOT NULL, -- 用户
Balance DECIMAL(19, 2) NOT NULL, -- 余额
LastUpdate SMALLDATETIME NOT NULL
)
GO
2. 准备数据
INSERT INTO dbo.BankAccount
VALUES ('9555500100071120', N'张三', 10000.00, GETDATE()) -- 北京分行账号
INSERT INTO dbo.BankAccount
VALUES ('9555507551227787', N'张三', 20000.00, GETDATE()) -- 深圳分行账号
GO
3. 查看数据
SELECT * FROM dbo.BankAccount
二、应用场景
假设张三在招商银行开设了两个账号,一个是招商银行北京分行,一个是招商银行深圳分行,两个账号的余额分别是10,000和20,000。
1. 张三在网上做了一笔交易,交易额100,买方小王通过银行汇款100到张三的北京分行的账号(见下面左图),柜台操作人员向张三账号存入100(事务一),然后系统些操作日志(假设需要10秒,WAITFOR DELAY '00:00:10')正在此时张三在ATM查了一下账号上余额(事务二),发现已经是10100,于是回去准备发货,但是事务一在写操作日志时超时,这是事务回滚,存款交易被取消,钱退给了小王,这样张三查到的账号余额事实上是事务一还没有提交的数据,导致张三错误的认为已经收到交易款项。
一个事务读到另外一个事务还没有提交的数据,我们称之为脏读。
解决方法:把事务隔离级别调整到READ COMMITTED,即把右上图中的SET TRAN ISOLATION LEVEL READ UNCOMMITTED更改成下图中的SET TRAN ISOLATION LEVEL READ COMMITTED。这时我们重复上面的动作会发现事务二会一直等到事务一执行完毕再返回结果,因为此时事务以已经把自己的更改ROLLBACK了,所以事务二可以返回正确的结果。
2. 张三先后两次查询某一账号的余额,在两次查询期间,小王完成了银行转账,导致两次的查询结果不同。
一个事务先后读取同一条记录,但两次读取的数据不同,我们称之为不可重复读。
解决方法:把事务隔离级别调整到REPEATABLE READ。在下图中使用SET TRAN ISOLATION LEVEL REPEATABLE READ。这时我们重复上面的动作会发现事务二会一直等到事务一执行完毕再返回结果。
3. 张三妻子先后两次查询张三招商银行所有账号的总余额,而在此期间张三在广州招商银行分行成功开设了一个账号,并存入5000,导致张三妻子两次查询的总余额不同,在此期间张三原有两个账号的余额均未发生改变。
一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读。
解决方法:把事务隔离级别调整到SERIALIZABLE。在下图中使用SET TRAN ISOLATION LEVEL SERIALIZABLE。这时我们重复上面的动作会发现事务二会一直等到事务一执行完毕再返回结果。