数据库的事物机制

事物:单个逻辑操作单元内一系列的操作,同时发生数据更新时,防止数据不一致。也就是事物保证除非逻辑单元内的所有操作都成功执行,否则不会发生数据更新。

事物属性:ACID

A(Atomicity):原子性。一般与单个事物相关的所有操作都有所关联,具有共同的目标。事物的原子性,保证这些操作要么全都执行,要么全不执行。

C(Consistency):一致性。数据库一致性(Database Consistency)是指事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。具体参考:如何理解数据库事物中一致性的概念

I(Isolation):隔离性。由并发事物所做的修改,必须与其他并发事物做的修改相隔离。也就是说,一个事物拿到的数据状态要么是并发事物执行前的状态,要么是执行后的状态,不会拿到中间状态。事物并发涉及到分布式数据库的概念,以后详谈。

D(Durability):永久性。
事物完成后对系统的影响是永久性的,即使是致命的操作也将保持一致。

数据库事物隔离机制:
一个大的应用系统,并发访问数据库是避免不了的,那么并发访问数据库可能出现以下情况:
1.A事物的更新覆盖了B事物的更新。(更新丢失)
2.A事物取到B事物更新的还未提交的数据,很有可能B事物涉及到的操作会回滚。那么A事物读取到的数据是不正确的(脏读)
3.A事物两次读取同一行数据,得到的结果不同,这是因为A事物第一次读取数据后,B事物对数据更新,A事物再次读取改事物,与第一次读取到的数据不同( 不可重复读)
包括以下两种情况:
(1) 虚读:事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。
(2) 幻读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

为了防止以上问题发生,标准SQL规范规定了4中事物隔离级别:

1.Read Uncommited :允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。

2.Read Commited:允许不可重复读取,但不允许脏读取。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。这可以通过“瞬间共享读锁”和“排他写锁”实现。

3.Repeatable Read:禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
4.Serializable:提供严格的事物级别,它要求事物序列执行,不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

隔离级别越高,数据安全性越高,但相应的并发性能会随之降低。

数据库事物锁机制:近日在自己做的项目中遇到这样一个问题:在A系统应用中对Q表进行更新操作,然后A系统调用B系统的服务,B系统服务中也对有Q表进行操作,此时A系统应用对Q表的操作还没有完成(A系统对Q表的操作和对B系统的调用在一个事物中),那么B系统应用会一直等A系统应用操作完成,才执行对Q表的操作,而此时A系统在调用B,这个事物还没结束,所以造成了互相等待,A系统对B系统调用超时。典型的死锁。(当时自己想到想重新开一个事物,但这样破坏了此应用逻辑,幸亏有前辈提醒)

排他锁 被加锁的对象只能被持有锁的事务读取和修改,其他事务无法在该对象上加其他锁,也不能读取和修改该对象
共享锁 被加锁的对象可以被持锁事务读取,但是不能被修改,其他事务也可以在上面再加共享锁。

特别的,对共享锁: 如果两个事务对同一个资源上了共享锁,事务A 想更新该数据,那么它必须等待 事务B 释放其共享锁。
在运用 排他锁 和 共享锁 对数据对象加锁时,还需要约定一些规则,例如何时申请 排他锁 或 共享锁、持锁时间、何时释放等。称这些规则为封锁协议(Locking Protocol)。对封锁方式规定不同的规则,就形成了各种不同的封锁协议

1、一级封锁协议 (对应 read uncommited)   
一级封锁协议是:事务 在对需要修改的数据上面(就是在发生修改的瞬间) 对其加共享锁(其他事务不能更改,但是可以读取-导致“脏读”),直到事务结束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。
一级封锁协议不能避免 丢失更新,脏读,不可重复读,幻读!

2、二级封锁协议 (对应read commited) 
二级封锁协议是:1)事务 在对需要更新的数据 上(就是发生更新的瞬间) 加 排他锁 (直到事务结束) , 防止其他事务读取未提交的数据,这样,也就避免了 “脏读” 的情况。2)事务 对当前被读取的数据 上面加共享锁 (当读到时加上共享锁),一旦读完该行,立即 释放该 该行的共享锁 - 从数据库的底层实现更深入的来理解,既是,数据库会对游标当前的数据上加共享锁 , 但是当游标离开当前行的时候,立即释放该行的共享锁。 
二级封锁协议除防止了“脏读”数据,但是不能避免 丢失更新,不可重复读,幻读 。
但在二级封锁协议中,由于读完数据后立即 释放共享锁,所以它不能避免可重复读 ,同时它也不能避免 丢失更新 ,如果事务A、B同时获取资源X,然后事务A先发起更新记录X,那么 事务B 将等待事务 A 执行完成,然后获得记录X 的排他锁,进行更改。这样事务 A 的更新将会被丢失。 具体情况如下:

事务A 事务B
读取X=100(同时上共享锁) 读取X=100(同时上共享锁)
读取成功(释放共享锁) 读取成功(释放共享锁)
UPDATE X=X+100 (上排他锁)
UPDATING A(等待事务A释放对X的排他锁)
事务成功(释放排他锁)X=200
UPDATE X=X+200(成功上排他锁)
事务成功(释放排他锁)X=300

由此可以看到,事务A的提交被事务B覆盖了,所以不能防止 丢失更新。
如果要避免 丢失更新,我们需要额外的操作, 对凡是读到的数据加 共享锁 和排他锁 ,这个往往需要程序员自己编程实现,比如在Oracle 中,需要加 SELECT FOR UPDATE 语句,表明,凡是该事务读到的数据,额外的加上排他锁,防止其他数据同一时间获取相同数据,这样就防止了 丢失更新 !

3、三级封锁协议 (对应reapetable read )
三级封锁协议是:二级封锁协议加上事务 在读取数据的瞬间 必须先对其加 共享锁 ,但是 直到事务结束才释放 ,这样保证了可重复读(既是其他的事务职能读取该数据,但是不能更新该数据)。
 
三级封锁协议除防止了“脏”数据 和不可重复读 。但是这种情况不能避免 幻读 和 丢失更新 的情况,在事务 A 没有完成之前,事务 B 可以新增数据,那么 当事务 A 再次读取的时候,事务B 新增的数据会被读取到,这样,在该封锁协议下,幻读 就产生了。 如果事务A 和 事务B 同时读取了资源X=100,同样,如果事务A先对X进行 更新X=X+100,等待事务A执行完成X=200,那么事务B 获得X的排他锁,进行更新 X=X+200,然后提交 X=300,同样A的更新被B所覆盖!( 如果要避免 丢失更新,我们需要额外的操作, 对凡是读到的数据加 共享锁 和排他锁 ,这个往往需要程序员自己编程实现,比如在Oracle 中,需要加 SELECT FOR UPDATE 语句,表明,凡是读到的数据,我会加 排他锁,防止其他数据同一时间获取相同数据) !

进阶:repeatable read 导致死锁的情况(即便是 不同的资源在相同的顺序下获取)。 比如 事务1 读取 A,同时 事务2 也读取 A,那么事务1和事务2 同时对 A 上了共享锁,然后事务1 要UPDATE A,而此时 事务2 也要 UPDATE A,这个时候 事务1 等待 事务2 释放其在 A 上的共享锁,然后 事务2 要等待 事务1 释放其在 A 上的共享锁,这样,事务1 和 事务2 相互等待,产生死锁!(SQL Server/DB2 里面有 UPDATE LOCK 可以解决这种情况,具体的思路是,在 repeatable read 的情况下,将读取的数据 上的 UPDATE 锁,介于 共享锁 和 排他锁之间的一种锁,该锁的作用是 当出现上面这种情况后,事务1 和 事务2 对 A 上的是 UPDATE 锁,那么谁先 要修改 A,那么该事务就会将 UPDATE 锁可以顺利升级为 排他锁对该数据进行修改!)

4、最强封锁协议(对应Serialization)
四级封锁协议是对三级封锁协议的增强,其实现机制也最为简单,直接对 事务中 所 读取 或者 更改的数据所在的表加表锁,也就是说,其他事务不能 读写 该表中的任何数据。这样所有的 脏读,不可重复读,幻读 ,都得以避免!
参考:数据库事务隔离级别和锁实现机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值