数据库事务

一个事务是可以被看作一系列SQL语句的集合。这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。

事务的ACID特性

原子性

原子性(Atomicity):事务对数据的修改,要么全都执行,要么全都不执行。一个事务对同一数据项的多次读取的结果一定是相同的。

一致性

一致性(Consistency):指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。

有些时候这种一致性由数据库的内部规则保证,例如数据的类型必须正确,数据值必须在规定的范围内,等等;另外一些时候这种一致性由应用保证的,例如一般情况下银行账务余额不能是负数,信用卡消费不能超过该卡的信用额度等。

隔离性

隔离性(Isolation):并发的事务是相互隔离的。即一个事务内部的操作及正在操作的数据必须封锁起来,对其他事务是不可见的。

持久性

持久性(Durability):只要事务成功结束,它对数据库所做的更新就必须永久保存下来。

即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

并发异常情况

脏读

脏读(Dirty Read):某个事务读取的数据是另一个事务正在处理的数据。而另一个事务可能会回滚,造成第一个事务读取的数据是错误的。

不可重复读

不可重复读(Non-repeatable Read):在一个事务里两次读入数据,但另一个事务已经更改了第一个事务涉及到的数据,造成第一个事务读入旧数据。

幻读

幻读(Phantom Read):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

丢失更新

丢失更新(Lost Update):

第一类:当两个事务更新相同的数据源,如果第一个事务被提交,第二个却被撤销回滚,那么连同第一个事务做的更新也被覆盖。
第二类:有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作可能被覆盖。

事务隔离级别

Read Uncommitted

读未提交数据。事务在执行过程既可以看到其他事务没有提交的新插入数据,也可看到其他事务已经提交的对已有记录的更新。

Read Uncommitted会出现脏读

Read Committed

读已提交数据。事务在执行过程中可以看到其他事务已提交的新插入记录,也可看到其他事务已提交的对已有记录的更新。

Read Committed不允许脏读,会出现不可重复读

Repeatable Read

可重复读。事务在执行过程中可以看到其他事务已提交的新插入记录,但不能看到其他事务已提交的对已有记录的更新。

Repeatable Read不允许脏读和不可重复读,会出现幻读

Serializable

串行化。一个事务在执行过程完全看不到其他事务对数据库所做的更新。当两个事务同时访问相同数据时,第一个事务必须等第二个事务完成后才能访问。

Serializable可以防止出丢失更新外所有的一致性问题。

注意:不可重复读和幻读的区别是,不可重复读对应的表的操作是更改(UPDATE和DELETE),而幻读对应的表的操作是插入(INSERT),两种的应对策略不一样。对于不可重复读,只需要采用行级锁防止该记录被更新即可,而对于幻读必须加个表级锁,防止在表中插入数据。

事务隔离的实现——锁

共享锁(S锁,读锁)

相互不阻塞,可以在同一时刻读取同一个资源,即在同一个资源上可以同时添加多个读锁。
共享锁不会阻止其他用户读,但是阻止其他的用户写和修改。

独占锁(X锁,写锁)

也叫排他锁,阻塞其他读锁或写锁,独占资源,在给定的时间内只允许一个用户写入。写是独占锁,可以有效的防止“脏读”。

锁粒度

锁粒度:加锁对象(资源)的大小。锁粒度的大小影响的系统的并发性和系统开销。当锁粒度较小,可以提高资源的并发使用,但是也增加的系统加锁的开销。反之,锁粒度越大,并发度越低,

表锁(table locks):在整张表上进行加锁,如MyISAM引擎。
行锁(row locks):在表中的一行或者多行数据记录上加锁。
页锁(page locks):目前只有BDB存储引擎使用。
元数据锁(Metadata Locks):MySQL5.5中引入,应用在表的metadata上。当一个线程使用表的时候,将会锁定整张表的metadata信息,不允许其他线程修改表结构。

悲观锁和乐观锁

针对于不同的业务场景,应该选用不同的并发控制方式。所以,不要把乐观并发控制和悲观并发控制狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。其实,在DBMS中,悲观锁正是利用数据库本身提供的锁机制来实现的。

悲观锁

悲观锁:指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。

乐观锁

乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

乐观锁,大多是基于数据版本( Version )记录机制实现。将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。