1. 事务的定义
事务是指不可划分,必须同时执行的工作。如果组成事务的各项工作均得到执行,则事务执行成功,若其中一项未执行,则事务回滚至初始状态,执行失败。事务包含4个基本属性(ACID):
- 原子性(AUTOMICITY),不可划分为更小单元
- 一致性(CONSISTENCY),事务对外是一个整体
- 隔离性(ISOLATION),其内部对其他事务是不可见的
- 持久性(DURABILITY),当断电恢复时,未成功提交的事务将回滚至初始状态
2. 并发模型和数据库锁定
事务的一致性和隔离性属性依靠数据库的锁定机制(LOCKING MECHANISM)实现,当一个事务需要另外一个进行中的事务的数据时,数据即锁定,直到此事务完成或是回滚。事务对锁定数据的访问必须等待该数据的释放,这在执行长事务时可能会影响整体性能及可扩展性。这种利用数据锁的并发数据访问模型被称作是悲观(PESSIMISTIC)模型。
在乐观(OPTIMISTIC)模型中,不使用数据锁,而是直接读取数据,当操作执行完成后,再读取该数据,如果数据已经改变,则抛出异常,并使用自定义的逻辑进行处理。
3. 事务隔离等级
完整的隔离在逻辑上是最优的,但在实际操作中可能会影响性能及可扩展性,例如在进行只读查询时,完整的隔离就不必要了。在微调事物的隔离性时,我们可能会如下情形:
- 脏读(DIRTY READ): 即可读取已被另一事务改变的值,但当事务遇到问题回滚时,就会出现问题。
- 不可重复读(NONREPEATABLE READ): 在两次读取另一事务中的数据值时发现数据不一致,因为两次读取数据之间事务已对其做了更改。
- 幻读(PHANTON READ): 一个事务读取另一事务要删除的数据或是第二次读取时读到了另一事务刚添加的数据
LEVEL | DIRTY READ | NONREPEATABLE READ | PHANTOM READ | CONCURRENCY MODEL |
---|
Read Uncommitted | Yes | Yes | Yes | None |
Read Committed with Locks | No | Yes | Yes | Pessimistic |
Read Committed with Snapshots | No | Yes | Yes | Optimistic |
Repeatable Read | No | No | Yes | Pessimistic |
Snapshot | No | No | No | Optimistic |
Serializable | No | No | No | Pessimistic |
在MYSQL中,只提供了4种事务隔离等级,即:
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
4. 单事务和分布式事务
在ADO.NET中,一个事务一般指在一个打开的链接中可处理的所有工作。如果需要一个事务同时包含多个链接,则需使用分布式事务。在WINDOWS中,处理分布式事务的管理工具为DTC即DISTRIBUTED TRANSACTION COORDINATOR。
5. 创建事务
事务分两种,一种是隐式事务,一种是显式事务。每一条SQL语句均以隐式事务的形式运行,也就是说即使你不显式的创建事务,事务也是存在的,一条语句即一个事务,这种机制保证了每一条SQL语句的完整执行,即要么成功执行,要么回滚。显示创建事务需在程序中添加代码。
6. 使用T-SQLT-SQL语句显示创建事务
示例如下:
SQL: Explicit Transaction
SET XACT_ABORT ON
BEGIN TRY
BEGIN TRANSACTION
--work code here
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
--cleanup code
END CATCH
注意事务的主体在TRY语句块中,在TRY语句块中可以使用存储过程。
7. 使用ADO.NET中的DbTransaction对象显示创建事务
示例代码如下:
static void Main()
{
ConnectionStringSettings cnSetting = ConfigurationManager.ConnectionStrings["TestApp.Properties.Settings.toucaiConnectionString"];
using (MySqlConnection cn = new MySqlConnection())
{
cn.ConnectionString = cnSetting.ConnectionString;
cn.Open();
using (
MySqlTransaction tran = cn.BeginTransaction())
{
try
{
//work code here
using (MySqlCommand cmd = cn.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandText = "SELECT count(*) FROM employee";
var count = (long)cmd.ExecuteScalar();
Console.WriteLine(count.ToString());
}
//if we made it this far, commit
tran.Commit();
}
catch (Exception xcp)
{
tran.Rollback();
//cleanup code
Console.WriteLine(xcp.Message);
}
}
}
}
在这段代码中,首先创建了一个链接对象(cn),然后此链接对象使用BeginTransaction()方法创建了一个事务,TRY语句块包含了事务的相关执行代码并提交事务,如有异常抛出,则CATCH语句块回滚事务,当然还可做些其他工作,如在备注代码行中修改一些变量的值。注意,命令对象(cmd)的Transaction属性值必须设置为链接生成的事务实例。此事物的作用范围仅限于TRY语句块(应还包括CATCH语句块),但因为这是由一个链接创建的,所以它不能被其他链接使用。
8. 设置事务隔离等级
每一个链接都可设置自己的事务隔离等级,设置事务隔离等级的方法有三种:
9. System.Transactions命名空间
System.Transactions Namespace提供了更为强大的事务支持,使用起来又更为简易,且天然支持分布式事务。
10. 使用TransactionScope创建事务
除了使用链接的BegainTransaction()方法创建事务外,还可以使用System.Transactions空间中的TransactionScope类创建事务,用此方法创建的事务被称作是“本地轻量级事务”,提供完整的分布式事务支持,更关键的是,这种事务是一种隐式的事务。用前一种方法创建的事务需显示的调用commit方法和rollback方法来提交和回滚,而通过TransactionScope创建的作用范围自动包含了一个事务,并可自动提交和回滚。示例代码如下:
using (TransactionScope ts = new
TransactionScope())
{
using (MySqlConnection cn = new MySqlConnection())
{
cn.ConnectionString = cnSetting.ConnectionString;
cn.Open();
//work code here
using (MySqlCommand cmd = cn.CreateCommand())
{
try
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = "UPDATE employee SET empID = 'MY002' WHERE empID = 'MY001'";
var count = cmd.ExecuteNonQuery();
cmd.CommandText = "UPDATE
employees SET empID = 'MY002' WHERE empID = 'MY001'";
count = cmd.ExecuteNonQuery();
Console.WriteLine(count.ToString());
ts.Complete();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
这段代码不会对数据库做任何操作,因为第二条SQL语句有错误,所以虽然第一条语句是正确的,但整个事务仍会回滚,数据没有被更改,证明了回滚机制运行正常。
代码由在一个USING语句中创建一个事务范围开始,在此范围内,如果创建一个链接,则这个链接自动事务范围自动将一个事务添加到此链接上,命令(COMMAND)也不用再指定其Transaction属性,如果有错误抛出或是没有找到Complete()语句,则事务进行回滚。否则,在见到Complete()语句时,完成整个事务。要注意,是否执行事务中(可执行)的语句,要看代码能不能见到Complete()语句,如果我们把ts.Complete()语句放在try-catch语句块之外,则这个事务的第一条语句仍是有效的,即事务虽遇异常抛出,但仍不会回滚。
Complete()语句只能执行一次,如果多次执行,会抛出异常。
因为链接是在事务范围内创建的,所以如果在此范围内创建多个链接,就可创建分布式事务。
10. 设置事务选项
使用事务范围创建事务时,可通过设置事务选项(Transaction Options)的方法来设置事务的隔离等级和事务超时时长(Transaction's Timeout Period)。设置方法有两个,一个是设置
TransactionOptions枚举值,另一个是使用事务范围的构造函数设置,
TransactionOptions枚举值的值有如下3个:
Required: 如果你的系统已经有一个事务正在运行,则将事务范围中的命令添加进行,如果没有,则创建一个,这是默认设置。
RequiresNew:总是新建一个新的事务,而不管是否有已经存在的事务正在运行。比如银行系统中已有一个事物用来处理存取款操作,如果此时需要有一个对账操作,则不须与存取款操作存在于一个事务中,即无论存取款操作是否成功,对账操作将独立运行。
Suppress: 忽略事务范围内的任何事务,用于创建一个非事务的环境,在此范围内的Compete()操作是无效的。
11. 使用分布式事务
在使用事务范围时,ADO.NET总是先生成轻量级事务,但在某些情况下,本地轻量级也会升级为分布式事务:
-
When a durable resource manager is used that doesn’t implement the IPromotableSinglePhaseNotification interface, such as SQL Server 2000
-
When two durable resource managers are enlisted in the same transaction
-
When the transaction spans multiple application domains