数据库事务与隔离级别

一、数据库事务

事务是数据库中最常见的概念

1、事务的特性

事务很经典的就是ACID四个特性:

  1. 原子性Atomic:

    原子性指组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有的操作执行成功,整个事务才提交,事务中任何一个数据库操作失败,已经执行的任何操作都必须撤销,让数据库返回到初始状态。

  2. 一致性Consistency:

    事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏。

    如从A账户转账100元到B账户,不管操作成功与否,A和B的存款总额是不变的。

  3. 隔离性Isolation:

    在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰。

    准确地说,并非要求做到完全无干扰,数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱。

  4. 持久性Durability:

    持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

在这些事务特性中,数据“一致性”是最终目标,其他的特性都是为达到这个目标的措施、要求或手段。

  • 对于原子性、一致性和持久性,数据库一般采用重执行日志来保证。

    重执行日志记录了数据库变化的每一个动作,数据库在一个事务中执行一部分操作后发生错误退出,数据库即可以根据重执行日志撤销已经执行的操作。此外,对于已经提交的事务,即使数据库崩溃,在重启数据库时也能够根据日志对尚未持久化的数据进行相应的重执行操作。

  • 关于隔离性,则更为复杂一点,和Java程序采用锁机制进行线程同步相类似,数据库采用锁机制来保证事务的隔离性。

    当多个事务试图对相同的数据进行操作时,只有持有锁的事务才能操作数据,直到前一个事务完成后,后面的事务才有机会对数据进行操作。

二、隔离性相关

对应的,就像Java中有若干死锁等并发性问题,数据库也有类似的问题。

1、数据库并发的问题

一个数据库可能拥有多个访问客户端,这些客户端都可以并发方式访问数据库。数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性。这些问题主要有两大类五种:

  • 数据读问题:
    1. 脏读
    2. 不可重复读
    3. 幻读
  • 数据更新问题
    1. 第一类丢失更新
    2. 第二类丢失更新

1.1、脏读dirty read

脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

1.2、不可重复读unrepeatable read

不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

1.3、幻读phantom read

A事务读取B事务提交的新增数据,这时A事务将出现幻象读的问题。幻象读一般发生在计算统计数据的事务中,举一个例子,假设银行系统在同一个事务中,两次统计存款账户的总金额,在两次统计过程中,刚好新增了一个存款账户,并存入100元,这时,两次统计的总金额将不一致:

不可重复读与幻读

幻读和不可重复读都是读取了另一条已经提交的事务,但却是两个不同的概念:

  • 不可重复读查询的都是同一个数据项(被更改),避免这种情况只要添加行级锁即可。
  • 而幻读针对的是一批数据整体(比如数据的个数),避免这种情况必须添加表级锁-将整个表锁定以防止新增数据。

1.4、第一类丢时更新

A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错误可能造成很严重的问题,通过下面的账户取款转账就可以看出来:

A事务在撤销时,“不小心”将B事务已经转入账户的金额给抹去了。

1.5、第二类丢时更新

A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失:

上面的例子里由于支票转账事务覆盖了取款事务对存款余额所做的更新,导致银行最后损失了100元,相反如果转账事务先提交,那么用户账户将损失100元。

2、数据库隔离级别

尽管数据库为用户提供了锁的DML操作方式,但直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁机制。只要用户指定会话的事务隔离级别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据资源添加上适合的锁。此外数据库还会维护这些锁,当一个资源上的锁数目太多时,自动进行锁升级以提高系统的运行性能,而这一过程对用户来说完全是透明的。

ANSI/ISO SQL 92标准定义了4个等级的事务隔离级别:

  1. Read uncommited 读未提交:级别最低,主要提供非阻塞读能力
  2. Read committed 读已提交
  3. Repeatable read 可重复读,MYSQL中默认的隔离级别
  4. Serializable 串行化:级别最高

在相同数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力是不同的:

隔离级别 脏读 不可重复读 幻读 第一类丢时更新 第二类丢失更新
Read uncommited 允许 允许 允许 不允许 允许
Read committed 不允许 允许 允许 不允许 允许
Repeatable read 不允许 不允许 允许 不允许 不允许
Serializable 不允许 不允许 不允许 不允许 不允许

事务的隔离级别和数据库并发性是对立的,两者此增彼长。一般来说,使用READ UNCOMMITED隔离级别的数据库拥有最高的并发性和吞吐量,而使用SERIALIZABLE隔离级别的数据库并发性最低。

3、InnoDB引擎的锁机制

之所以以InnoDB为主介绍锁,是因为InnoDB支持事务,支持行锁和表锁用的比较多,Myisam不支持事务,只支持表锁

  • 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
  • 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
  • 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
  • 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

说明:

  1. 共享锁和排他锁都是行锁,意向锁都是表锁,应用中我们只会使用到共享锁和排他锁,意向锁是mysql内部使用的,不需要用户干预。
  2. 对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁,事务可以通过以下语句显示给记录集加共享锁或排他锁:
    • 共享锁(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE。
    • 排他锁(X):SELECT * FROM table_name WHERE … FOR UPDATE。
  3. InnoDB行锁是通过给索引上的索引项加锁来实现的,因此InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

三、参考地址

http://www.iteye.com/topic/1124043

http://www.cnblogs.com/fjdingsd/p/5273008.html

http://blog.csdn.net/pingnanlee/article/details/41824389