数据库事务特性ACID
数据库事务特征,即 ACID:
A Atomicity 原子性
事务是一个原子性质的操作单元,事务里面的对数据库的操作要么都执行,要么都不执行,
C Consistent 一致性
在事务开始之前和完成之后,数据都必须保持一致状态,必须保证数据库的完整性。也就是说,数据必须符合数据库的规则。
I Isolation 隔离性
数据库允许多个并发事务同事对数据进行操作,隔离性保证各个事务相互独立,事务处理时的中间状态对其它事务是不可见的,以此防止出现数据不一致状态。可通过事务隔离级别设置:包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)
D Durable 持久性
一个事务处理结束后,其对数据库的修改就是永久性的,即使系统故障也不会丢失。
并发控制
并发控制描述了数据库事务隔离以保证数据正确性的机制。为了保证并行事务执行的准确执行,数据库和存储引擎在设计的时候着重强调了并发控制这一点。典型的事务相关机制限制数据的访问顺序(执行调度)以满足可序列化 和可恢复性。限制数据访问意味着降低了执行的性能,并发控制机制就是要保证在满足这些限制的前提下提供尽可能高的性能。经常在不损害正确性的情况下,为了达到更好的性能,可序列化的要求会减低一些,但是为了避免数据一致性的破坏,可恢复性必须保证。
两阶段锁是关系数据库中最常见的提供了可序列化和可恢复性的并发控制机制,为了访问一个数据库对象,事务首先要获得这个对象的锁。对于不同的访问类型(如对对象的读写操作)和锁的类型,如果另外一个事务正持有这个对象的锁,获得锁的过程会被阻塞或者延迟。
问题产生原因
如果数据库只提供单人访问,那么不会产生这些问题。但是数据库是给多人访问的,所以会出现以下几种不确定的情况。
读现象举例
脏读Dirty reads
脏读是指在一个事务处理过程里读取了另一个未提交事务中的数据。
举例:事务一、B同时查余额。事务二向账户余额增加10元,但是提交失败,导致事务回滚。
时间 | 事务一 | 事务二 |
1 | 查询账户余额为100元 | 查询账户余额为100元 |
2 | 增加了10元(此时并没有commit) | |
3 | 查询账户余额为110元 | |
4 | Commit | Rollback(因某种原因未提交成功) |
最终结果:因为事务二提交失败,所以账户余额还是100元。但是事务一查询到未提交的数据,产生了脏读。
不可重复读Non-repeatable reads
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询,却返回了不同的数据值。这是由于在查询间隔中,数据被另一个事务修改并提交了。
举例:事务一、B同时查余额。事务二向账户余额增加10元。并且提交成功
时间 | 事务一 | 事务二 |
1 | 查询账户余额为100元 | 查询账户余额为100元 |
2 | 增加了10元 | |
3 | commit | |
4 | 查询账户余额为110元 | |
5 | Commit |
|
事务一在读取某一数据,而事务二立马修改了这个数据并且提交事务给数据库,事务一再次读取该数据就得到了不同的结果,发送了不可重复读。
不可重复读和脏读的区别
脏读是某一事务读取了另一个事务未提交的脏数据
不可重复读则是读取了另一事务已提交的数据。
在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对同一个数据,A和B因为查询时间的不同导致最后读取的数据结果不同,可能就会产生问题。
幻读 Phantom reads
时间 | 事务一 | 事务二 |
1 | 查询数据个数,共2条数据,id分别为1、2 | 查询数据个数,共2条数据,id分别为1、2 |
2 | 新增一条id=3的数据 Insert into table (id) value (3) | |
3 | commit | |
4 | 再次查询数据个数,查到3条数据 | |
5 | Commit |
|
刚开始A读数据明明为2条,但是第二次却查到3条。就像产生了幻觉一样。
幻读与不可重复读的区别
相同点:
两者都是已提交后的数据。
不同点:
范围不同:不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
针对行为不同:不可重复读针对数据的修改造成的读不一致,而幻读针对数据的插入和删除造成的读不一致,如同发生幻觉一样。
读现象解决方案
针对并发事务,要想解决脏读、不可重复读、幻读等读问题,数据库系统引入了隔离操作,以保证各个线程(事务)获取数据的准确性。
事务隔离级别
在数据库事务的ACID四个属性中,隔离性是一个最常放松的一个。为了获取更高的隔离等级,数据库系统的锁机制或者多版本并发控制机制都会影响并发。 应用软件也需要额外的逻辑来使其正常工作。
很多数据库管理系统定义了不同的“事务隔离等级”来控制锁的程度。在很多数据库系统中,多数的数据库事务都避免高等级的隔离等级(如可序列化)从而减少对系统的锁定开销。程序员需要小心的分析数据库访问部分的代码来保证隔离级别的降低不会造成难以发现的代码bug。相反的,更高的隔离级别会增加死锁发生的几率,同样需要编程过程中去避免。
ANSI/ISO SQL定义的标准隔离级别如下:
隔离级别从低到高
- Read uncommttied未提交读
- Read committed提交读
- Repeatable reads可重复读
- Serializable可串行化
Read uncommttied未提交读
未提交读(READ UNCOMMITTED)是最低的隔离级别。通过名字我们就可以知道,在这种事务隔离级别下,一个事务可以读到另外一个事务未提交的数据。
未提交读的数据库锁情况(实现原理)
事务在读数据的时候并未对数据加锁。
务在修改数据的时候只对数据增加行级共享锁。
现象
事务1读取某行记录时,事务2也能对这行记录进行读取、更新(因为事务一并未对数据增加任何锁)
当事务2对该记录进行更新时,事务1再次读取该记录,能读到事务2对该记录的修改版本(因为事务二只增加了共享读锁,事务一可以再增加共享读锁读取数据),即使该修改尚未被提交。
事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(因为事务一对数据增加了共享读锁,事务二不能增加排他写锁进行数据的修改)
举例
举例还是举上面的例子
脏读例子
时间 | 事务一 | 事务二 |
1 | 查询账户余额为100元 | 查询账户余额为100元 |
2 | 增加了10元(此时并没有commit) | |
3 | 查询账户余额为110元 | |
4 | Commit | Rollback(因某种原因未提交成功) |
事务一一共查询了两次。在第二次的查询过程中,事务二对数据进行了修改,并未提交。
但是事务一读取到了事务二未提交的内容,这种线程称为脏读。
所以,未提交读会导致脏读
Read committed提交读
提交读(READ COMMITTED)也可以翻译成读已提交,通过名字也可以分析出,在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。
提交读的数据库锁情况
事务对当前被读取的数据加 行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。
现象:
事务1在读取某行记录的整个过程中,事务2都可以对该行记录进行读取(因为事务一对该行记录增加行级共享锁的情况下,事务二同样可以对该数据增加共享锁来读数据。)。
事务1读取某行的一瞬间,事务2不能修改该行数据,但是,只要事务1读取完改行数据,事务2就可以对该行数据进行修改。(事务一在读取的一瞬间会对数据增加共享锁,任何其他事务都不能对该行数据增加排他锁。但是事务一只要读完该行数据,就会释放行级共享锁,一旦锁释放,事务二就可以对数据增加排他锁并修改数据)
事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(事务一在更新数据的时候,会对该行数据增加排他锁,知道事务结束才会释放锁,所以,在事务二没有提交之前,事务一都能不对数据增加共享锁进行数据的读取。所以,提交读可以解决脏读的现象)
举例
不可重复读例子
时间 | 事务一 | 事务二 |
1 | 查询账户余额为100元 | 查询账户余额为100元 |
2 | 增加了10元 | |
3 | commit | |
4 | 查询账户余额为110元 | |
5 | Commit |
|
幻读例子
时间 | 事务一 | 事务二 |
1 | 查询数据个数,共2条数据,id分别为1、2 | 查询数据个数,共2条数据,id分别为1、2 |
2 | 新增一条id=3的数据 Insert into table (id) value (3) | |
3 | commit | |
4 | 再次查询数据个数,查到3条数据 | |
5 | Commit |
|
在提交读隔离级别中,在事务二提交之前,事务一不能读取数据。只有在事务二提交之后,事务一才能读数据。
但是从上面的例子中我们也看到,事务一两次读取的结果并不一致,所以提交读不能解决不可重复读和幻读的读现象。
小结
因为:提交读这种隔离级别保证了读到的任何数据都是提交的数据
所以:解决了脏读问题
但是:不能解决 不可重复读和幻读
因为:每次数据读完之后其他事务可以修改刚才读到的数据。
Repeatable reads可重复读
可重复读(REPEATABLE READS),由于提交读隔离级别会产生不可重复读的读现象。所以,比提交读更高一个级别的隔离级别就可以解决不可重复读的问题。这种隔离级别就叫可重复读(这名字起的是不是很任性!!)
可重复读的数据库锁情况
事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加 行级共享锁,直到事务结束才释放;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。
现象
事务1在读取某行记录的整个过程中,事务2都可以对该行记录进行读取(因为事务一对该行记录增加行级共享锁的情况下,事务二同样可以对该数据增加共享锁来读数据。)。
事务1在读取某行记录的整个过程中,事务2都不能修改该行数据(事务一在读取的整个过程会对数据增加共享锁,直到事务提交才会释放锁,所以整个过程中,任何其他事务都不能对该行数据增加排他锁。所以,可重复读能够解决不可重复读的读现象)
事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(事务一在更新数据的时候,会对该行数据增加排他锁,知道事务结束才会释放锁,所以,在事务二没有提交之前,事务一都能不对数据增加共享锁进行数据的读取。所以,提交读可以解决脏读的现象)
举例
不可重复读例子(在这里,这个问题已被解决)
时间 | 事务一 | 事务二 |
1 | 查询账户余额为100元 | 查询账户余额为100元 |
2 | Commit | |
3 | 增加了10元 | |
4 |
| commit |
5 | 查询账户余额为110元 | |
6 | Commit |
|
在上面的例子中,只有在事务一提交之后,事务二才能更改该行数据。
所以,只要在事务一从开始到结束的这段时间内,无论他读取该行数据多少次,结果都是一样的。
从上面的例子中我们可以得到结论:可重复读隔离级别可以解决不可重复读的读现象。
但是可重复读这种隔离级别中,还有另外一种读现象他解决不了,那就是幻读。看下面的幻读例子:
时间 | 事务一 | 事务二 |
1 | 查询数据个数,共2条数据,id分别为1、2 | 查询数据个数,共2条数据,id分别为1、2 |
2 | 新增一条id=3的数据 Insert into table (id) value (3) | |
3 | commit | |
4 | 再次查询数据个数,查到3条数据 | |
5 | Commit |
|
上面的两个事务执行情况及现象如下:
1.事务一进行查询操作,查询数据库个数,工2条数据。这时,他会给这查询到的2条数据增加行级共享锁。任何其他事务无法更改查询到的这2条记录。
2.事务二往表中插入一条数据操作。因为此时没有任何事务对表增加表级锁,所以,该操作可以顺利执行。
3.事务一再次执行查询操作,却查到3条数据,增加的这条正是事务二刚刚插入的那条。
所以,事务一的两次范围查询结果并不相同。这也就是我们提到的幻读。
Serializable可串行化
可序列化(Serializable)是最高的隔离级别,前面提到的所有的隔离级别都无法解决的幻读,在可序列化的隔离级别中可以解决。
我们说过,产生幻读的原因是事务一在进行范围查询的时候没有增加范围锁(range-locks:给SELECT 的查询中使用一个“WHERE”子句描述范围加锁),所以导致幻读。
可序列化的数据库锁情况
事务在读取数据时,必须先对其加 表级共享锁 ,直到事务结束才释放;
事务在更新数据时,必须先对其加 表级排他锁 ,直到事务结束才释放。
现象
事务1正在读取A表中的记录时,则事务2也能读取A表,但不能对A表做更新、新增、删除,直到事务1结束。(因为事务一对表增加了表级共享锁,其他事务只能增加共享锁读取数据,不能进行其他任何操作)
事务1正在更新A表中的记录时,则事务2不能读取A表的任意记录,更不可能对A表做更新、新增、删除,直到事务1结束。(事务一对表增加了表级排他锁,其他事务不能对表增加共享锁或排他锁,也就无法进行任何操作)
虽然可序列化解决了脏读、不可重复读、幻读等读现象。但是序列化事务会产生以下效果:
1.无法读取其它事务已修改但未提交的记录。
2.在当前事务完成之前,其它事务不能修改目前事务已读取的记录。
3.在当前事务完成之前,其它事务所插入的新记录,其索引键值不能在当前事务的任何语句所读取的索引键范围中。
小结
四种事务隔离级别从隔离程度上越来越高,但同时在并发性上也就越来越低。之所以有这么几种隔离级别,就是为了方便开发人员在开发过程中根据业务需要选择最合适的隔离级别。
隔离级别vs读现象
隔离级别 | 脏读 | 不可重复读 | 幻读 |
未提交读Read uncommitted | 可能发生 | 可能发生 | 可能发生 |
提交读Read committed | - | 可能发生 | 可能发生 |
可重复读Repeatable read | - | - | 可能发生 |
可序列化Serializable | - | - | - |
可序列化(Serializable)隔离级别不等同于可串行化(Serializable)。可串行化调度是避免以上三种现象的必要条件,但不是充分条件。
“可能发生”表示这个隔离级别会发生对应的现象,“-”表示不会发生。
隔离级别vs 锁持续时间
在基于锁的并发控制中,隔离级别决定了锁的持有时间。"C"-表示锁会持续到事务提交。 "S" –表示锁持续到当前语句执行完毕。如果锁在语句执行完毕就释放则另外一个事务就可以在这个事务提交前修改锁定的数据,从而造成混乱。
隔离级别l | 写操作 | 读操作 | 范围操作 (...where...) |
未提交读Read uncommitted | S | S | S |
提交读Read committed | C | S | S |
可重复读Repeatable read | C | C | S |
可序列化Serializable | C | C | C |
MySQL事务隔离简单介绍
在MySQL里共有四个隔离级别,分别是:
- Read uncommttied(读未提交数据)
- Read committed(读已提交数据)
- Repeatable read(可重复读)
- Serializable(可串行化)
在MySQL数据库里,默认的事务隔离级别是Repeatable read(可重复读)。
相关命令
1.查看当前会话隔离级别
SELECT @@tx_isolation;
2.查看系统当前隔离级别
SELECT @@global.tx_isolation;
3.设置当前会话隔离级别
set session transaction isolation level read uncommitted;
4.设置系统当前隔离级别
set global transaction isolation level read uncommitted;
5.命令行,开始事务时
set autocommit=off;
或者
start transaction;
注意
但是这里有一点需要注意的是数据库的默认引擎是InnoDB在使用InnoDB引擎下,即便设定的事务隔离级别是Repeatable read,也不会出现数据幻读现象。
-原因:MySQL InnoDB存储引擎,实现的是基于多版本的并发控制协议——MVCC (Multi-Version Concurrency Control) 所以在Repeatable Read (RR)隔离级别下不存在幻读。
参考:
http://www.luxinzhi.com/database/502.html
https://zh.wikipedia.org/wiki/%E4%BA%8B%E5%8B%99%E9%9A%94%E9%9B%A2
http://www.cnblogs.com/zhoujinyi/p/3437475.html
https://www.jianshu.com/p/4e3edbedb9a8
http://www.cnblogs.com/zhoujinyi/p/3437475.html
http://www.hollischuang.com/archives/943