事务
mysql InnoDB事务模型
InnoDB和 AUTOCOMMIT
在InnoDB中,所有用户行为都在事务内发生。如果自动提交模式被允许,每个SQL语句在它自己上形成一个单独的事务。MySQL总是带着允许自动提交来开始一个新连接。
如果自动提交模式被用SET AUTOCOMMIT = 0关闭,那么我们可以认为一个用户总是有一个事务打开着。一个SQL COMMIT或ROLLBACK语句结束当前事务并且一个新事务开始。两个语句都释放所有在当前事务中被设置的InnoDB锁定。一个COMMIT语句意味着在当前事务中做的改变被生成为永久的,并且变成其它用户可见的。一个ROLLBACK语句,在另一方面,撤销所有当前事务做的修改。
如果连接有被允许的自动提交,通过用明确的START TRANSACTION或BEGIN语句来开始一个事务,并用COMMIT或者ROLLBACK语句来结束它,这样用户仍旧可以执行一个多重语句事务。InnoDB和TRANSACTION ISOLATION LEVEL
按照SQL:1992 事务隔离级别,InnoDB默认是可重复读的(REPEATABLE READ)。MySQL/InnoDB 提供SQL标准所描述的所有四个事务隔离级别。你可以在命令行用--transaction-isolation选项,或在选项文件里,为所有连接设置默认隔离级别。例如,你可以在my.inf文件的[mysqld]节里类似如下设置该选项:globally
[mysqld]
transaction-isolation = {READ-UNCOMMITTED | READ-COMMITTED
| REPEATABLE-READ | SERIALIZABLE}用户可以用SET TRANSACTION语句改变单个会话或者所有新进连接的隔离级别。它的语法如下:
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
{READ UNCOMMITTED | READ COMMITTED
| REPEATABLE READ | SERIALIZABLE}
默认的行为是为下一个(未开始)事务设置隔离级别。如果你使用GLOBAL关键字,语句在全局对从那点开始创建的所有新连接(除了不存在的连接)设置默认事务级别。你需要SUPER全县来做这个。使用SESSION 关键字集为将来在当前连接上执行的事务设置默认事务级别。
任何客户端都能自由改变会话隔离级别(甚至在事务的中间),或者为下一个事务设置隔离级别。
你可以用下列语句查询全局和会话事务隔离级别:InnoDB中每个隔离级别的详细描述如下:
READ UNCOMMITTED
SELECT语句以非锁定方式被执行,但是一个可能更早期版本的记录会被用到。因此,使用这个隔离级别,比如,读是不连贯的。着也被称为“脏读”(dirty read)。另外,这个隔离级别象READ COMMITTED一样作用。
READ COMMITTED
一个有些象Oracle的隔离级别。所有SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MOD语句仅锁定索引记录,而不锁定记录前的间隙,因而允许随意紧挨着已锁定的记录插入新记录。UPDATE和DELETE语句使用一个带唯一搜索条件的唯一的索引仅锁定找到的索引记录,而不包括记录前的间隙。在范围类型UPDATE和DELETE语句,InnoDB必须对范围覆盖的间隙设置next-key锁定或间隙锁定以及其它用户做的块插入。这是很必要的,因为要让MySQL复制和恢复起作用,“幽灵行”必须被阻止掉。
REPEATABLE READ
这是InnoDB的默认隔离级别。带唯一搜索条件使用唯一索引的SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE, UPDATE 和DELETE语句只锁定找到的索引记录,而不锁定记录前的间隙。用其它搜索条件,这些操作采用next-key锁定,用next-key锁定或者间隙锁定锁住搜索的索引范围,并且阻止其它用户的新插入。
在持续读中,有一个与之前隔离级别重要的差别:在这个级别,在同一事务内所有持续读读取由第一次读所确定的同一快照。这个惯例意味着如果你在同一事务内发出数个无格式SELECT语句,这些SELECT语句对相互之间也是持续的,SERIALIZABLE
这个级别类似REPEATABLE READ,但是所有无格式SELECT语句被隐式转换成SELECT ... LOCK IN SHARE MODE。
事务隔离级别的现实
事务隔离(Transaction Isolation)是
指交织在一起发生的事务之间相互影响的程度。它决定了在其他事务访问和更新同一份数据
时,一个事务对更新所允许的可见程度。关系数据库系统(DBMS),EJB,Spring 都允许设
定事务隔离级别。然而,这样的设置,是数据库和应用服务器实现相依赖的。应用服务器可
能支持多种隔离级别,但对应的数据库必须支持这些同样的隔离级别,这些设置才可能真正
生效。
事务隔离同时是数据库并发度(concurrency)和一致性(consistency)的函数。当我们提高
了事务隔离度,我们实际上降低了数据库并发程度,但提高了一致性。下面的图示说明了三
者之间的关系
该设置能直接影响应用的性能和数据完整性。例如,对于高性能的应用,诸如信用卡处理程
序,您可以通过降低隔离级别以提高并发度(但这样会损害数据完整性)。对低并发、需要
高度数据完整性的金融类应用,您需要提高隔离以别,以增强整体数据一致性(但损害了性
能)。大多数应用服务器和数据库都具有平衡并发度和一致性的一个默认设置值。然而,在
EJB 和 Spring 中,您能够通过更改相关设置在需要的时候优化应用。
EJB 和 Spring 均支持四种主要的隔离级别,这些设置(隔离度从低到高)分别是:
TransactionReadUncommitted 读取未提交
TransactionReadCommitted 读取已提交
TransactionRepeatableRead 可重复读
TransactionSerializable 可序列化
TransactionReadUncommitted
这一隔离级别允许事务读取其他事务在提交到
数据库之前产生的未提交更改。为了展示这一隔离级别是如何工作的,我们假设当前一笔特
定的股票交易,在每股 90 美金时进行。两个事务(事务 A 和事务 B)试图同时访问同一数
据,事务 A 进行更新操作,而事务 B 进行读取操作。下图展示了在设置
TransactionReadUncommitted 时两个事务是如何交互的:
注意,在图示中,事务 A 于时间点 t2 对数据库进行了一次更新,而事务 B 能够在时间点 t3
读取这一数据,读取的时候该数据的修改是并未被提交进数据库的。如图所示,事务 A 做
出的修改无法与事务 B 相隔离。如果事务 A 回滚了这一修改,事务 B 读取到的数据极有可
能是错误的。这一隔离级别设置违反了基本的 ACID 准则,很多数据库厂商并不支持这一隔
离级别(包括 Oracle)。
脏读模拟
创建表
<span style="font-size:18px;">CREATE TABLE t (i INT) ENGINE = InnoDB;</span>
打开俩个会话 设置隔离级别
<span style="font-size:18px;">set session tx_isolation='read-uncommitted';
set global tx_isolation='read-uncommitted';
select @@global.tx_isolation,@@tx_isolation;
set autocommit=off;</span>
A会话
B会话
A会话读到了B会话尚未提交的记录导致脏读.
TransactionReadCommitted
据提交。同样使用上面的例子,假设当前以90 美金的价格交易股票,两笔事务(事务A 和
事务B)试图在同一时间访问同一数据,事务A 做更新而事务B 做读取。下图展示了在设置
TransactionReadCommitted 时两个事务是如何交互的:
修改。这是一个非常好的隔离设置;它允许事务B 访问数据(支持并发),并同时隐藏了其
他事务对同一份数据的未提交更改,直至更改提交。这是大多数数据库缺省的隔离设定,很
多数据库厂商都支持它。
此级别会导致不可重复读
A会话
B会话
可以看到readcommited 虽然解决了脏读问题,但是导致了新的不可重复读的问题.
事务A在两次查询的数据不一致,因为在两次查询之间事务B更新了一条数据。已提交读只允许读取已提交的记录,但不要求可重复读。
TransactionRepeatableRead
一旦在某一事务中读取了数据库的一个值集,在后续的每次查询操作中都读到同样的值(除
非此事务拿到这些数据的读写锁,并自行更改了数据)。为了演示这个隔离级别,我们假设
有一个select 语句查询数据库,以获取今天迄今为止买入的所有股票。事务A 在主管查询的
事务B 的进行过程中插入了一些数据。下图展示了在设置为TransactionRepeatableRead 隔离
级别时事务间是如何交互的:
回同样的查询结果,不论其他事务的最新提交结果如何。仅当事务B 提交了之后,它才能够
看到最近购入的那一笔股票。请注意在这样的隔离设置下,目标数据在被查询和更改时被同
时加上读锁和写锁,使用这一隔离级别应该小心,因为在Repeatable Read 隔离级别下,一
个事务如果要更改数据,而这一数据被其他事务读取时,此事务需要等待占用数据事务提交
的操作(或直接返回失败)。
TransactionSerializable
迭”起来,以致同一时间点仅仅有一个事务具备访问目标数据的权力(我们在本节随后的部
分可以看到,这在Oracle 数据库不完全是成立的)。如果使用这一设置,并发度将会收到非
常大的影响,但数据一致性将会得到极大提高。使用上面同样的例子,假设当前以90 美金
的价格交易股票,两笔事务(事务A 和事务B)试图在同一时间访问同一数据,事务A 做更
新而事务B 做读取。下图展示了在设置TransactionSerializable 时两个事务是如何交互的:
Oracle 处理该设置稍有不同。由于Oracle 使用数据版本(data versioning)技术,它实际上
并不真正挂起事务B。反之,如果在事务B 的进行过程中,事务B 试图访问目标数据,Oracle
将抛出ORA-08177 错误,指出对该事务无法序列化访问数据。