c# 怎么在代码里面执行数据库事务

c# 怎么在代码里面执行数据库事务,第1张

OracleTransaction类的概述

应用程序通过针对OracleConnection对象调用 BeginTransaction 来创建OracleTransaction对象。对OracleTransaction对象执行与该事务关联的所有后续 *** 作(例如提交或中止该事务)。OracleTransaction的成员主要有:

属性:

Connection,指定与该事务关联的OracleConnection对象;

IsolationLevel,指定该事务的IsolationLevel;枚举类型,用于对事物的锁定,取值有Chaos、ReadCommited、ReadUncommited、RepeatableRead、Serializable、Unspecified。

方法:

Commit,提交SQL数据库事务;

Rollback , 从挂起状态回滚事务;

第一个示例:

public void 函数名称()

{

string strUpdateSql1 = "update 表名 set XH='...' where XH='...' and LCGZ_ID='...'

string strUpdateSql2 = "update 表名 set XH='...' where BZGZ_ID='...'

OracleConnection conn = new OracleConnection()

conn.ConnectionString = "数据库连接字符串"

conn.Open()

OracleTransaction updateProcess = conn.BeginTransaction()try

{

OracleCommand comm1 = conn.CreateCommand()

comm1.CommandText = strUpdateSql1

comm1.Transaction = updateProcess

comm1.ExecuteNonQuery() OracleCommand comm2 = conn.CreateCommand()

comm2.CommandText = strUpdateSql2

comm2.Transaction = updateProcess

comm2.ExecuteNonQuery() updateProcess.Commit()

}

catch()

{

updateProcess.Rollback()

}

finally

{

conn.Close()

}

}

第二个示例:

public static bool 函数名2(int fID)

{

OracleConnection conn = new OracleConnection()

conn.ConnectionString = "数据库连接字符串"

conn.Open()

OracleTransaction deleteProcess = conn.BeginTransaction() try

{

ArrayList alObjects = GetObjects()

foreach(object o in alObjects)

{

OracleCommand tmpComm = conn.CreateCommand()

tmpComm.Transaction = deleteProcess

tmpComm.CommandText = "delete from 表1名 where BZGZ_ID="+o.ID

tmpComm.ExecuteNonQuery() OracleCommand tmpComm2 = conn.CreateCommand()

tmpComm2.Transaction = deleteProcess

tmpComm2.CommandText = "delete from 表2名 where BZGZ_ID="+o.ID

tmpComm2.ExecuteNonQuery()

}

OracleCommand comm = conn.CreateCommand()

comm.Transaction = deleteProcess

comm.CommandText = "delete from 表3名 where LCGZ_ID="+fID

comm.ExecuteNonQuery() deleteProcess.Commit()

return true

}

catch()

{

deleteProcess.Rollback()

return false

}

finally

{

conn.Close()

}

方法A 里面 调用 方法B ,然后调用了方法C ,只要在 A 方法上面加上事务,B,C 不开启新的事务,使用A的 事务 那么不管 A ,B,C 任何地方异常都会让事务回滚,并且 A,B,C 的数据变动会 一起提交。ACB 的 为提交 数据,相互可见。

分布式事务,还是 上面 A,B,C 的例子

但是 A,B ,C是 3 个独立的程序了, A ,B,C中 不是本地调用,而是 RPC 调用( 不管是 什么RPC 都是走的网络 ,不是本地调用(指的本程序内,不需要过网络 ,不过需要过IP地址 )),

这时候本地事务明显不生效了。

在来模拟 服务的 A 方法里面调用了 ,B服务的 B方法,然后调用的 C服务的 C方法。

然后,

1 如果 A 调用 B ,C 之前报错,A 被事务回滚 ,B ,C 没有调用, 这没问题。

2 如果是 A 调用 了 B,C 以后 A 抛出异常, 那么 A 回滚了 ,B ,C 执行了 就提交了。 数据 不一致了

3 如果 A 调用 B ,然后调用 C ,C 报错了 ,C 回滚,C 调用异常 被 A 感知,A 也会回滚,B 执行完就自己提交了,不会跟着一起回滚,数据不一致。

 有人会说 我避免 3 个服务相互调用 ,我每次 只有2 个服务相互联系

 我 只会 A 调用 B ,然后 B 调用C

  1 A 调用 B ,在方法的最后面 ,A 前面如果报错 了,B不会被调用 ,如果前面没有出错 ,调用B,B失败了,B回滚,异常传递个A,A 也回滚 , A B 之间的数据就 一致了 ,B,C 同理。

上面说的没错,理想环境下,如果只保持 2 层调用,并且调用 下一个服务 都在 事务提交前一刻执行 那么完全没问题,但是 网络环境 有一种极端情况。

A 发起一次调用B 可以拆分成几部,A请求B --->B 收到请求 做自己的 --->然后B 响应 A -->A 收到 B 的响应

这时候 如果 B做了,提交了自己, 然后响应B的时候超时, A 那边抛出响应超时, A 不知道 B 是 做了 还是 没做 ,A 收到 超时异常 就回滚,然后 数据就不一致了。

有人又说了,我们是是内网 无限带宽 几乎不会出络异常,不考虑。网络不考虑,但是如果 B 做了提交了事务,然后B挂了,A 依旧没有收到响应,依旧要回滚,还是 会出数据不一致的问题。即便这些都是 小概率,然后 服务 只能保持 2 层调用,在大型 系统中依旧 明显不适用 ,因为这样会 链 会很长,调用会很不方便,然后 这个 链还必须是同步执行的。效率差。

解释 分布式环境 为什么会出 一致性问题,所以分布式事务就是来解决这些问题的。

分布式事务 在我看来有4种

第1种 2pc事务,3pc事务,包括 TX-LCN 的 LCN 模式, 可以叫做 长锁定多段式分布式事务

第2中 TCC 这种补偿事务, 可以叫做短锁定多段式分布式事务,特地额 try 阶段 会独立提交,不会多个节点相互 等待,comfrom 阶段 也是相互独立的。 比第一种效率高,但是 一个方法写三遍,一个逻辑 分三个方向写,编码麻烦。

第3中,基于消息:效率没有 长锁定多段式分布式事务 那么低, 编码没有 TCC 那么麻烦。 但是需要注意的编程细节也挺多的。 总的来说算一种比较综合的解决方案,消息机制 有个 很明显的缺陷,它强调保证最终一致性,并不能同时回滚。 A 服务 发送给B服务的的消息,或者发出去的确认消息,只能完成, B 做失败了,只能重试(并且保持幂等), 不能让 A 一起回滚。 只有 主动 发起方可以终止 和回滚 这个分布式事务,入股A 提交以后,只能硬着头皮走到底。

当启动Binlog后,事务会产生Binlog Event,这些Event被看做事务数据的一部分。因此要保证事务的Binlog Event和InnoDB引擎中的数据的一致性。所以带Binlog的CrashSafe要求MySQL宕机重启后能够保证:

- 所有已经提交的事务的数据仍然存在。

- 所有没有提交的事务的数据自动回滚。

- 所有已经提交了的事务的Binlog Event也仍然存在。

- 所有没有提交事务没有记录Binlog Event。

这些要求很好理解,如果重启后数据还在,但是Binlog Event没有了,就没办法复制到其他节点上了。如果重启后,数据没了,但是Binlog Event还在,那么不存在的数据就会被复制到其他节点上,从而导致主从的不一致。

为了保证带Binlog的CrashSafe,MySQL内部使用的两阶段提交(Two Phase Commit)。

2 - MySQL的Two Phase Commit(2PC)

在开启Binlog后,MySQL内部会自动将普通事务当做一个XA事务来处理:

- 自动为每个事务分配一个唯一的ID

- COMMIT会被自动的分成Prepare和Commit两个阶段。

- Binlog会被当做事务协调者(Transaction Coordinator),Binlog Event会被当做协调者日志。

想了解2PC,可以参考文档:【https://en.wikipedia.org/wiki/Two-phase_commit_protocol。】

- 分布式事务ID(XID)

使用2PC时,MySQL会自动的为每一个事务分配一个ID,叫XID。XID是唯一的,每个事务的XID都不相同。XID会分别被Binlog和InnoDB记入日志中,供恢复时使用。MySQ内部的XID由三部分组成:

- 前缀部分

前缀部分是字符串"MySQLXid"

- Server ID部分

当前MySQL的server_id

- query_id部分

为了保证XID的的唯一性,数字部分使用了query_id。MySQL内部会自动的为每一个语句分配一个query_id,全局唯一。

参考代码:sql/xa。h的struct xid_t结构。

- 事务的协调者Binlog

Binlog在2PC中充当了事务的协调者(Transaction Coordinator)。由Binlog来通知InnoDB引擎来执行prepare,commit或者rollback的步骤。事务提交的整个过程如下:

1. 协调者准备阶段(Prepare Phase)

告诉引擎做Prepare,InnoDB更改事务状态,并将Redo Log刷入磁盘。

2. 协调者提交阶段(Commit Phase)

2.1 记录协调者日志,即Binlog日志。

2.2 告诉引擎做commit。

注意:记录Binlog是在InnoDB引擎Prepare(即Redo Log写入磁盘)之后,这点至关重要。

在MySQ的代码中将协调者叫做tc_log。在MySQL启动时,tc_log将被初始化为mysql_bin_log对象。参考sql/binlog.cc中的init_server_components():

if (opt_bin_log) tc_log= &mysql_bin_log

而在事务提交时,会依次执行:

tc_log->prepare();

tc_log->commit();

参考代码:sql/binlog.cc中的ha_commit_trans()。当mysql_bin_log是tc_log时,prepare和commit的代码在sql/binlog.cc中:

MYSQL_BIN_LOG::prepare();

MYSQL_BIN_LOG::commit();

-协调者日志Xid_log_event

作为协调者,Binlog需要将事务的XID记入日志,供恢复时使用。Xid_log_event有以下几个特点:

- 仅记录query_id

因为前缀部分不变,server_id已经记录在Event Header中,Xid_log_event中只记录query_id部分。

- 标志事务的结束

在Binlog中相当于一个事务的COMMIT语句。

一个事务在Binlog中看起来时这样的:

Query_log_event("BEGIN")DML产生的events Xid_log_event

- DDL没有BEGIN,也没有Xid_log_event 。

- 仅InnoDB的DML会产生Xid_log_event

因为MyISAM不支持2PC所以不能用Xid_log_event ,但会有COMMIT Event。

Query_log_event("BEGIN")DML产生的eventsQuery_log_event("COMMIT")

问题:Query_log_event("COMMIT")和Xid_log_event 有不同的影响吗?

- Xid_log_event 中的Xid可以帮助master实现CrashSafe。

- Slave的CrashSafe不依赖Xid_log_event

事务在Slave上重做时,会重新产生XID。所以Slave服务器的CrashSafe并不依赖于Xid_log_event 。Xid_log_event 和Query_log_event("COMMIT"),只是作为事务的结尾,告诉Slave Applier去提交这个事务。因此二者在Slave上的影响是一样的。

3 - 恢复(Recovery)

这个机制是如何保证MySQL的CrashSafe的呢,我们来分析一下。这里我们假设用户设置了以下参数来保证可靠性:

- 恢复前事务的状态

在恢复开始前事务有以下几种状态:

- InnoDB中已经提交

根据前面2PC的过程,可知Binlog中也一定记录了该事务的的Events。所以这种事务是一致的不需要处理。

- InnoDB中是prepared状态,Binlog中有该事务的Events。

需要通知InnoDB提交这些事务。

- InnoDB中是prepared状态,Binlog中没有该事务的Events。

因为Binlog还没记录,需要通知InnoDB回滚这些事务。

- Before InnoDB Prepare

事务可能还没执行完,因此InnoDB中的状态还没有prepare。根据2PC的过程,Binlog中也没有该事务的events。 需要通知InnoDB回滚这些事务。

- 恢复过程

从上面的事务状态可以看出:恢复时事务要提交还是回滚,是由Binlog来决定的。

- 事务的Xid_log_event 存在,就要提交。

- 事务的Xid_log_event 不存在,就要回滚。

恢复的过程非常简单:

- 从Binlog中读出所有的Xid_log_event

- 告诉InnoDB提交这些XID的事务

- InnoDB回滚其它的事务


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/sjk/6847712.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-03-29
下一篇 2023-03-29

发表评论

登录后才能评论

评论列表(0条)

保存