欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ADO.NET学习之SqlTransaction

程序员文章站 2022-03-09 20:03:51
...

ADO.NET学习之SqlTransaction

事务是一组必须全部成功或全部失败的操作。事务的目标是保证数据总能处于有效一致的状态。
事务有4个被称为ACID属性的特征:

  • Atomic(原子性):事务中所有的步骤必须同时成功或失败。只有事务中的所有步骤全部完成了,才能认为一个事务结束
  • Consist(一致性):事务使底层数据库在稳定状态间转换
  • Isolated(隔离性):每个事务都是独立的实体。一个事务不能影响同时运行的其他事务
  • Durable(持久性):在事务成功之前,事务产生的变化永久的存储在媒质上,通常是硬盘上。必须维护日志以保证即使出现硬件故障或网路失败,数据库也能恢复到有效状态

以转账场景为例,假设有如下的2个账户:

ADO.NET学习之SqlTransaction

从账户A1转10元到A2账户,先从A1账户中扣除10,再在A2账户中添加10。如果从A1的账户中扣除了钱,而A2账户中确没有添加钱,这种情况就是不可接受的。所以,2步要么都要成功,要么就都要失败,如果一个成功,一个失败,就要回滚操作,来保持数据的完整性。在ADO.NET中可通过transactions事物来完成。

第一步,创建表,并添加数据

Create Table Accounts
(
     AccountNumber nvarchar(10) primary key,
     CustomerName nvarchar(50),
     Balance int
)

Insert into Accounts values('A1', 'Mark', 100)
Insert into Accounts values('A2', 'Steve', 100)

第二步,创建页面

<div style="font-family: Arial">
<table border="1" style="background: brown; color: White">
    <tr>
        <td>
            <b>Account Number </b>
        </td>
        <td>
            <asp:Label ID="lblAccountNumber1" runat="server"></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblAccountNumber2" runat="server"></asp:Label>
        </td>
    </tr>
    <tr>
        <td>
            <b>Customer Name </b>
        </td>
        <td>
            <asp:Label ID="lblName1" runat="server"></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblName2" runat="server"></asp:Label>
        </td>
    </tr>
    <tr>
        <td>
            <b>Balance </b>
        </td>
        <td>
            <asp:Label ID="lblBalance1" runat="server"></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblBalance2" runat="server"></asp:Label>
        </td>
    </tr>
</table>
<br />
<asp:Button ID="btnTransfer" runat="server"
            Text="Transfer $10 from Account A1 to Account A2"
            OnClick="btnTransfer_Click" />
<br />
<br />
<asp:Label ID="lblMessage" runat="server" Font-Bold="true"></asp:Label>
</div>

第三步,转账事件

    protected void btnTransfer_Click(object sender, EventArgs e)
    {
        string cs = ConfigurationManager.ConnectionStrings["DatabaseConnectionString"].ConnectionString;
        using (SqlConnection con = new SqlConnection(cs))
        {
            con.Open();
            SqlTransaction transaction = con.BeginTransaction();

            try
            {
                SqlCommand cmd = new SqlCommand(
                    "Update Accounts set Balance = Balance - 10 where AccountNumber = 'A1'",
                    con,
                    transaction);
                cmd.ExecuteNonQuery();
                cmd = new SqlCommand(
                    "Update Accounts1 set Balance = Balance + 10 where AccountNumber = 'A2'",
                    con,
                    transaction);
                cmd.ExecuteNonQuery();

                transaction.Commit();
                lblMessage.ForeColor = System.Drawing.Color.Green;
                lblMessage.Text = "Transaction committed";
            }
            catch
            {
                transaction.Rollback();
                lblMessage.ForeColor = System.Drawing.Color.Red;
                lblMessage.Text = "Transaction rolled back";
            }
            GetAccountsData();
        }
    }

创建SqlCommand的时候,使用public SqlCommand(string cmdText, SqlConnection connection, SqlTransaction transaction);方法
开始事务SqlTransaction transaction = con.BeginTransaction();
提交事务transaction.Commit();
回滚事务transaction.Rollback();

为了测试事务可以在Commit()方法之前,产生一个异常,如

throw new ApplicationException();

隔离级别

隔离级别决定了事务对其他事务影响的数据的敏感度。例如,默认情况下当两个事务独立运行时,在第一个事务结束前,第一个事务插入的数据对其他事务不可见。

隔离级别的概念和锁的概念紧密相关,因为确定事务的隔离级别就是确定所需的锁的类型。共享锁是事务读取数据库中的数据时产生的锁。当表、行或某个范围内有共享锁时,其他事务就不可以修改相应的数据,但多个用户可以使用共享锁并发读取数据。独占锁禁止多个事务同时修改数据。当事务更新数据且没有其他事务锁定数据的时候,会产生独占锁。当有独占锁时,其他用户不能读取或更新数据。

在SQL Server存储过程中,使用SET TRANSACTION ISOLATION LEVEL命令来设置隔离级别。在ASP.NET中,可以向Connection.BeginTransaction()方法传入IsolationLevel枚举值。

IsolationLevel枚举值

描述
ReadUncommitted 无共享锁,也不会有独占锁。要和任意符合特定条件的数据一起工作时,如果不管该数据是否已经提交就使用这种隔离级别,它可能会导致脏数据读,但可以提升性能
ReadCommitted 数据被事务读时会产生共享锁。避免了脏数据读,但数据在事务结束前已经被修改。这样可能会产生非重复读或者虚幻行。它是SQL Server默认的隔离级别
Snapshot 保存一份事务正在访问的数据的副本。因此,一个事务不会看到其他事务所做的修改。这个隔离级别减少了堵塞,因为当其他事务正在读取被快照隔离事务锁锁定的数据时,它可以从数据副本中读取数据。该选项仅被SQL Server 2005支持并需要通过数据库级别的设定才能够启用
RepeatableRead 查询中所涉及的所有数据均被加上共享锁。这样避免了其他人修改数据,同时也避免了不可重复的读。不过,还是可能出现虚幻行
Serializable 在所使用的数据上的一系列锁禁止了其他用户在该范围内更新或插入行。它是唯一可以删除虚幻行的隔离级别。但是,它会给用于并发访问带来非常消极的影响,所以在多用户场景很少使用

脏读是指读取了其他尚未提交的事务中的数据,但该事务可能被回滚
不可重复读如果允许不可重复读,那么在同一个事务执行多次查询可能会得到不同的数据。这是因为事务进行过程中只读数据并不能阻止其他用户修改数据。为了防止不可重复读,数据库服务器需要锁定事务读取的行
幻读时指在初始读取中没有出现但在相同事务内后续读取出现的行。事务进行过程中其他用户插入记录,就可能出现幻读。为了防止幻读,事务进行过程中查询数据库时要根据WHERE子句使用一个范围锁

大多数情况下,不可重复读和幻读只是小问题,使用锁阻止它们发生并发的代价有点太高了,不太值得。默认情况下,ReadCommitted 对大多数事务都适用

隔离级别对比

隔离级别 脏读 不可重复读 虚幻数据 并发性
未提交读(ReadUncommitted) 最佳
提交读(ReadCommitted )
快照(Snapshot )
重复读(RepeatableRead) 一般
序列化(Serializable) 很差

资料