一、事务:
事务是数据库管理系统执行过程中的一个 逻辑单位 ,由一个有限的数据库操作序列构成
二、事务的目的:
1、为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
2、当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
当一个事务被提交给了DBMS(数据库管理系统),则DBMS需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态(要么全执行,要么全都不执行);
同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。
三、事务的4个属性:
1、原子性(Atomicity):
事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
原子性消除了事务在执行期间不可避免的不一致状态对事务执行前后的数据库状态的影响。
2、一致性(Consistency):
事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
一致性就是数据库的数据状态符合数据库所描述的业务逻辑和规则。比如最简单的一条一致性规则,银行账户存款余额不能是负值。
例如资金从账户A转到B可以被定义成两个单独的程序完成,这两个程序依次执行可以保持一致性,但是这两个程序自身都不是把数据库从一个一致的状态转到一个新的一致状态,所以它们都不是事务。
在事务执行过程中,必要时允许暂时的不一致性,因为无论是A的取出操作在前,还是B的存入操作在前,这两个操作必然有一个先后顺序,两个操作之间就会产生不一致。这种暂时的不一致虽然是必须的,但是在故障发生的时候,很可能导致问题的发生,于是这个时候我们的原子性就发挥作用了。
https://blog.youkuaiyun.com/amghost/article/details/17651891
3、隔离性(Isolation):
多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
4、持久性(Durability):
一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。
http://www.hollischuang.com/archives/898
四、如果不考虑隔离性,会出现的问题
1、脏读,又称无效数据的读出
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。比如:事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的。
2、不可重复读
不可重复读,是指在数据库访问中,一个事务在自己没有更新数据库数据的情况,同一个查询操作执行两次或多次的结果应该是一致的;如果不一致,就说明为不可重复读。
一种更易理解的说法是:在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。
即:同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
3、幻读
幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。
那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样.一般解决幻读的方法是增加范围锁RangeS,锁定检锁范围为只读,这样就避免了幻读。
4、丢失更新
两个不同事务同时获得相同数据,然后在各自事务中同时修改了该数据,那么先提交的事务更新会被后提交事务的更新给覆盖掉,这种情况事务A的更新就被覆盖掉了、丢失了。
http://www.hollischuang.com/archives/900
五、事务的隔离级别和实现原理
ANSI/ISO SQL定义的标准隔离级别有四种,从高到底依次为:可序列化(Serializable)、可重复读(Repeatable reads)、提交读(Read committed)、未提交读(Read uncommitted)。
隔离级别的实现本质上是数据库对封锁方式规定不同的规则,就形成了各种不同的封锁协议,也就是不同的隔离级别。
1、未提交读(Read uncommitted)[ 一级封锁协议]
最低的隔离级别。在这种事务隔离级别下,一个事务可以读到另外一个事务未提交的数据。
原理:
事务在对需要修改的数据上面(就是在发生修改的瞬间) 对其加 共享锁 (其他事务不能更改,但是可以读取,从而导致“脏读”),直到事务结束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。
这种隔离级别不能避免丢失更新,脏读,不可重复读,幻读!
2、提交读(Read committed)[ 二级封锁协议]
也可以翻译成读已提交,指的是在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。
原理:
1、事务在对需要更新的数据上(就是发生更新的瞬间),加 行级排他锁(直到事务结束) ,防止其他事务读取未提交的数据,这样,也就避免了“脏读”的情况。
2、事务对当前被读取的数据上面加 共享锁 (当读到时加上共享锁),一旦读完该行,立即释放该行的共享锁。从数据库的底层实现更深入的来理解,既是:数据库会对游标当前的数据上加共享锁,但是当游标离开当前行的时候立即释放该行的共享锁。
提交读防止了“脏读”数据,但是不能避免 丢失更新,不可重复读,幻读 。
在这个隔离级别中,由于读完每一行的数据后立即释放共享锁,所以它不能避免可重复读。
同时它也不能避免丢失更新 。如果事务A、B同时获取资源X,然后事务A先发起更新记录X,那么 事务B 将等待事务 A 执行完成,然后获得记录X 的排他锁,进行更改。这样事务 A 的更新将会被丢失。如果要避免丢失更新,我们需要额外的操作, 对凡是读到的数据加 共享锁和排他锁
3、可重复读(Repeatable reads)[ 三级封锁协议]
可重复读(REPEATABLE READS),由于提交读隔离级别会产生不可重复读的读现象。所以,比提交读更高一个级别的隔离级别就可以解决不可重复读的问题。这种隔离级别就叫可重复读
原理:
可重复读是:提交读加上事务,在读取数据的瞬间 必须先对其加共享锁, 但是直到事务结束才释放 ,这样保证了可重复读(即:其他的事务只能读取该数据,但是不能更新该数据)。
可重复读隔离级别解决了脏读和不可重复读,但是不能避免幻读和丢失更新的情况。
在事务 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所覆盖!如果要避免 丢失更新,我们需要额外的操作, 对凡是读到的数据加共享锁 和排他锁 ,这个往往需要程序员自己编程实现。
进阶:
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、可序列化(Serializable)[ 最强封锁协议]
可序列化(Serializable)是最高的隔离级别,前面提到的所有的隔离级别都无法解决的幻读,在可序列化的隔离级别中可以解决。
原理:
事务在读取数据时,必须先对其加 表级共享锁 ,直到事务结束才释放;
事务在更新数据时,必须先对其加 表级排他锁 ,直到事务结束才释放。
虽然可序列化解决了脏读、不可重复读、幻读等读现象。但是序列化事务会产生以下效果:
1.无法读取其它事务已修改但未提交的记录。
2.在当前事务完成之前,其它事务不能修改目前事务已读取的记录。
3.在当前事务完成之前,其它事务所插入的新记录,其索引键值不能在当前事务的任何语句所读取的索引键范围中。
http://comedsh.iteye.com/blog/698733
http://www.hollischuang.com/archives/943