根据微软《使用快照隔离》的内容,运行了其中的例子并给出中文注释。
以下示例通过尝试访问锁定的数据,演示不同隔离级别的行为,并非要在生产代码中使用。
该代码连接到 SQL Server 中的 AdventureWorks 示例数据库上,并创建一个名为 TestSnapshot 的表,然后插入一行数据。该代码使用 ALTER DATABASE Transact-SQL 语句对数据库启用快照隔离,但是不设置 READ_COMMITTED_SNAPSHOT 选项,让默认的 READ COMMITTED 隔离级别的行为生效。然后,该代码执行下列操作:
开始但是不完成 sqlTransaction1,sqlTransaction1 使用 SERIALIZABLE 隔离级别开始更新事务。这样做的结果是锁定表。
打开第二个连接,并使用 SNAPSHOT 隔离级别开始第二个事务,读取 TestSnapshot 表中的数据。因为启用了快照隔离,此事务可以读取在开始 sqlTransaction1 之前存在的数据。
打开第三个连接,并使用 READ COMMITTED 隔离级别开始一个事务,尝试读取表中的数据。在这种情况下,代码无法读取数据,因为代码在第一个事务中无法通过在表上放置的锁进行读取,因而超时。如果使用 REPEATABLE READ 和 SERIALIZABLE 隔离级别,因为这些隔离级别也无法通过第一个事务中放置的锁,因而会出现同样的结果。
打开第四个连接,并使用 READ UNCOMMITTED 隔离级别开始一个事务,对 sqlTransaction1 中未提交的值执行脏读。如果第一个事务未提交,数据库中永远不会真正存在此值。
回滚第一个事务,并通过删除 TestSnapshot 表以及禁用 AdventureWorks 数据库的快照隔离来进行清理。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.Data.SqlClient; namespace Snapshot { class Program { static void Main(string[] args) { /*在字符串前加一个“@”字符的意思是取消字符中转意字符的功能。 string s1 = @"a//b"; string s2 = "a//b"; Console.WriteLine(s1+"|"+s2); */ /*测试数据库连接 string connectionString = @"Data Source=XUWEI/SQLEXPRESS;Initial Catalog=demo;User ID=sa;Password=sa12345"; SqlConnection conn = new SqlConnection(connectionString); conn.Open(); SqlCommand cmd = conn.CreateCommand(); cmd.CommandText = "select * from test"; SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { Console.WriteLine(reader.GetValue(0).ToString() + "," + reader.GetValue(1).ToString()); } */ //定义数据库连接字符串。 string connectionString = @"Data Source=XUWEI/SQLEXPRESS;Initial Catalog=AdventureWorks;User ID=sa;Password=sa12345"; using (SqlConnection connection1 = new SqlConnection(connectionString)) { // Drop the TestSnapshot table if it exists,如果要创建的数据库存在,则删除。 connection1.Open(); SqlCommand command1 = connection1.CreateCommand(); command1.CommandText = "IF EXISTS " + "(SELECT * FROM sys.tables WHERE name=N'TestSnapshot') " + "DROP TABLE TestSnapshot"; try { command1.ExecuteNonQuery(); } catch (Exception ex) { Console.WriteLine(ex.Message); } // Enable Snapshot isolation,设置数据库使其支持快照隔离(Snapshot isolation) command1.CommandText = "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION ON"; command1.ExecuteNonQuery(); // Create a table named TestSnapshot and insert one row of data,创建表并插入一行数据。 command1.CommandText = "CREATE TABLE TestSnapshot (ID int primary key, valueCol int)"; command1.ExecuteNonQuery(); command1.CommandText = "INSERT INTO TestSnapshot VALUES (1,1)"; command1.ExecuteNonQuery(); // Begin, but do not complete, a transaction to update the data // with the Serializable isolation level, which locks the table // pending the commit or rollback of the update. The original // value in valueCol was 1, the proposed new value is 22. /*开始,但是并不完成实物,一个事务在Serializable隔离级别上更新数据, * 这样就对这张表加了锁,知道事务提交或者回滚为止。原始数据是(1,1),现在将变成(1,22)。 */ SqlTransaction transaction1 = connection1.BeginTransaction(IsolationLevel.Serializable); command1.Transaction = transaction1; command1.CommandText = "UPDATE TestSnapshot SET valueCol=22 WHERE ID=1"; command1.ExecuteNonQuery(); // Open a second connection to AdventureWorks,打开第二个连接 using (SqlConnection connection2 = new SqlConnection(connectionString)) { connection2.Open(); // Initiate a second transaction to read from TestSnapshot // using Snapshot isolation. This will read the original // value of 1 since transaction1 has not yet committed. /*初始化第二个事务,在快照隔离级别上读取表中的数据,这样读到的数据将是原始的数据, * 因为事务1还没有提交。 */ SqlCommand command2 = connection2.CreateCommand(); SqlTransaction transaction2 = connection2.BeginTransaction(IsolationLevel.Snapshot); command2.Transaction = transaction2; command2.CommandText = "SELECT ID, valueCol FROM TestSnapshot"; SqlDataReader reader2 = command2.ExecuteReader(); while (reader2.Read()) { Console.WriteLine("Expected 1,1 Actual " + reader2.GetValue(0).ToString() + "," + reader2.GetValue(1).ToString()); } reader2.Close(); transaction2.Commit(); } // Open a third connection to AdventureWorks and // initiate a third transaction to read from TestSnapshot // using ReadCommitted isolation level. This transaction // will not be able to view the data because of // the locks placed on the table in transaction1 // and will time out after 4 seconds. // You would see the same behavior with the // RepeatableRead or Serializable isolation levels. /*打开第三个数据库连接,创建一个在ReadCommitted隔离级别上读取表的事务。 * 这个事务不能读取表中的数据,这是因为事务1已经对表加了锁。数据访问将在4妙后超时。 * 在RepeatableRead 和 Serializable 隔离级别上也同样不能读取表中的数据。 */ using (SqlConnection connection3 = new SqlConnection(connectionString)) { connection3.Open(); SqlCommand command3 = connection3.CreateCommand(); SqlTransaction transaction3 = connection3.BeginTransaction(IsolationLevel.ReadCommitted); command3.Transaction = transaction3; command3.CommandText = "SELECT ID, valueCol FROM TestSnapshot"; command3.CommandTimeout = 4; try { SqlDataReader sqldatareader3 = command3.ExecuteReader(); while (sqldatareader3.Read()) { Console.WriteLine("You should never hit this."); } sqldatareader3.Close(); transaction3.Commit(); } catch (Exception ex) { Console.WriteLine("Expected timeout expired exception: " + ex.Message); transaction3.Rollback(); } } // Open a fourth connection to AdventureWorks and // initiate a fourth transaction to read from TestSnapshot // using the ReadUncommitted isolation level. ReadUncommitted // will not hit the table lock, and will allow a dirty read // of the proposed new value 22 for valueCol. If the first // transaction rolls back, this value will never actually have // existed in the database. /*打开第四个数据库连接,创建一个在ReadUncommitted隔离级别上读取表的事务。 * ReadUncommitted不会触碰到表中的锁,而且能够“脏读”到被修改但是没有被提交的数据22。 * 如果事务1回滚了,数据库中将不会存在22这个值。 */ using (SqlConnection connection4 = new SqlConnection(connectionString)) { connection4.Open(); SqlCommand command4 = connection4.CreateCommand(); SqlTransaction transaction4 = connection4.BeginTransaction(IsolationLevel.ReadUncommitted); command4.Transaction = transaction4; command4.CommandText = "SELECT ID, valueCol FROM TestSnapshot"; SqlDataReader reader4 = command4.ExecuteReader(); while (reader4.Read()) { Console.WriteLine("Expected 1,22 Actual " + reader4.GetValue(0).ToString() + "," + reader4.GetValue(1).ToString()); } reader4.Close(); transaction4.Commit(); } // Roll back the first transaction,回滚事务1。 transaction1.Rollback(); } // CLEANUP // Delete the TestSnapshot table and set // ALLOW_SNAPSHOT_ISOLATION OFF /*清理 * 删除TestSnapshot表和集合 * 置ALLOW_SNAPSHOT_ISOLATION 状态为OFF。 */ using (SqlConnection connection5 = new SqlConnection(connectionString)) { connection5.Open(); SqlCommand command5 = connection5.CreateCommand(); command5.CommandText = "DROP TABLE TestSnapshot"; SqlCommand command6 = connection5.CreateCommand(); command6.CommandText = "ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION OFF"; try { command5.ExecuteNonQuery(); command6.ExecuteNonQuery(); } catch (Exception ex) { Console.WriteLine(ex.Message); } } Console.WriteLine("Done!"); } } }