事务Transaction

本文深入解析事务的概念,探讨ACID特性及其在并发控制中的作用,分析脏读、非重复读、幻读及丢失修改等问题,并详述不同隔离级别的特点与应用。

为什么写这系列的文章

在日常的工作和开发中,接触最多的便是与数据库打交道,无论你使用什么框架进行开发都绕不开事务的管理. 在Java开发中你可能会接触很多ORM框架,无论是Hibernate、MyBatis、还是Spring Jdbc 都会遇到事务的相关操作,再到中大型项目,你还会遇到单一数据源本地事务、多数据源本地事务、分布式事务、分布式多数据源事务等各种奇葩环境,数据的一致性也会面临各种挑战,在这时如何游刃有余的处理数据一致性就考验你对事务的理解,也正是这系列文章写作的真正原因.

由于我换工作原因,先后在峰鸟科技、人人网、国家电网待过一段时间,先后接触了不同的ORM框架

峰鸟科技
在这里由于SQL比较灵活,同时考虑到性能问题,这里直接使用的Spring JDBC,为了提高开发效率,我也开发了一个简单ORM框架,用来将javabean 和 数据库表信息进行关联,提供了简单的增删改查和分页功能,这时候也是我初步接触框架的开发,接触反射、代理等功能

人人网
在这里先后使用 Python 和 Java 进行开发,用过MyBatis、Django-orm、sqlalchemy、JPA、Jade等ORM框架。其中Jade是人人网自行开发的ORM框架,使用简单,支持条件化语句、支持多数据源切换和多数据源事务功能,相对MyBatis来说,使用方式和功能更加友好,强大。

国家电网
在这里主要使用的MyBaits,第一次看见Spring事务注解可以添加到类上而不是方法上,这也说明你懂的还是不多

通过不同的经历,也先后接触到了JDBC事务、Spring事务、Hibernate事务、MyBaits事务、多数据源事务、分布式事务的使用和解决方案,这里总结一下

事务概念

事务(Transaction):一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。

数据库事务(Database Transaction):是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。

ACID

  1. atomicity 原子性
    原子性:操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
  2. consistency 一致性
    一致性:事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
  3. isolation 隔离性
    隔离性:在该事务执行的过程中,无论发生的任何数据的改变都应该只存在于该事务之中,对外界不存在任何影响。只有在事务确定正确提交之后,才会显示该事务对数据的改变。其他事务才能获取到这些改变后的数据。
  4. durability 持久性
    持久性:当事务正确完成后,它对于数据的改变是永久性的。

并发事务导致的问题

在许多事务处理同一个数据时,如果没有采取有效的隔离机制,那么并发处理数据时,会带来一些的问题。

脏读(Dirty Read)

当一个事务读取另一个事务尚未提交的修改时,产生脏读。

同一事务内不是脏读。 一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚,也就是说读取出的数据其实是错误的。

非重复读(Nonrepeatable Read)

一个事务对同一行数据重复读取两次,但是却得到了不同的结果。同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。

幻读(Phantom Reads)

事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发生幻像读问题。

丢失修改(Lost Update)

第一类:当两个事务更新相同的数据源,如果第一个事务被提交,第二个却被撤销,那么连同第一个事务做的更新也被撤销。

第二类:有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。

事务隔离

为了兼顾并发效率和异常控制,在标准SQL规范中,定义了4个事务隔离级别.

读未提交(Read Uncommitted)

直译就是"读未提交",意思就是即使一个更新语句没有提交,但是别的事务可以读到这个改变。

Read Uncommitted允许脏读。

读已提交(Read Committed)

直译就是"读提交",意思就是语句提交以后,即执行了 Commit 以后别的事务就能读到这个改变,只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别。

Read Commited 不允许脏读,但会出现非重复读。

可重复读(Repeatable Read):

直译就是"可以重复读",这是说在同一个事务里面先后执行同一个查询语句的时候,得到的结果是一样的。

Repeatable Read 不允许脏读,不允许非重复读,但是会出现幻象读。

串行读(Serializable)

直译就是"序列化",意思是说这个事务执行的时候不允许别的事务并发执行。完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

Serializable 不允许不一致现象的出现。

通过不同的隔离级别,可以防止一些并发事务问题,同时级别越高则相应性能越低,这个设置需要根据实际场景进行设置.

下一篇:MySQL数据库事务以及存储引擎
系列文章:
事务Transaction
Spring Cloud 分布式事务管理
Spring Cloud 分布式事务管理(二)2pc/3pc

这里写图片描述

### 3.1 驱动兼容性问题 Qt 对 MySQL 的事务支持依赖于底层驱动的实现。如果使用的 MySQL 驱动(`qsqlmysql.dll`)版本与 MySQL 客户端库不兼容,可能导致事务功能不可用。某些版本的 MySQL Connector/C(如 8.0.x)在 Qt 的默认驱动中并未完全实现事务控制功能,导致调用 `QSqlDatabase::transaction()` 时返回 `false`。 解决方法是重新编译 Qt 的 MySQL 插件,确保使用的 `libmysql.dll` 与驱动编译时的版本一致。例如,使用 `mysql-connector-c-6.1.11-winx64.zip` 替代更高版本的 Connector/C,可提高兼容性[^2]。 ### 3.2 驱动加载失败导致事务不可用 如果 Qt 无法正确加载 `QMYSQL` 驱动,事务操作将无法执行。即使代码中指定了 `"QMYSQL"` 驱动,如果驱动未正确部署或依赖库缺失(如 `libmysql.dll` 未放置在可执行文件目录或系统路径中),`QSqlDatabase::addDatabase("QMYSQL")` 调用将失败,但不会立即抛出异常。此时调用 `transaction()` 方法将无效。 确保部署时将 `libmysql.dll` 和 Qt 的数据库插件路径(如 `plugins/sqldrivers`)一同打包,并在程序启动时检查支持的数据库驱动: ```cpp qDebug() << QSqlDatabase::drivers(); ``` 若输出中未包含 `"QMYSQL"`,说明驱动未正确加载,事务功能将不可用[^3]。 ### 3.3 存储引擎不支持事务 MySQL 的事务支持依赖于存储引擎。如果目标表使用的是 `MyISAM` 等不支持事务的引擎,即使 Qt 和 MySQL 驱动配置正确,事务操作(如 `BEGIN`, `COMMIT`, `ROLLBACK`)也不会生效。此时调用 `transaction()` 方法可能返回 `true`,但在提交或回滚时不会产生预期效果。 应确保操作的表使用支持事务的引擎(如 `InnoDB`),可通过以下 SQL 语句验证: ```sql SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'your_database' AND TABLE_NAME = 'your_table'; ``` 若返回的 `ENGINE` 不是 `InnoDB`,需执行以下语句进行转换: ```sql ALTER TABLE your_table ENGINE = InnoDB; ``` 此操作可确保事务操作在数据库层面被正确支持[^1]。 ### 3.4 事务隔离级别与并发控制 MySQL 的事务行为受隔离级别影响。默认情况下,MySQL 使用 `REPEATABLE-READ` 隔离级别,可能影响事务的可见性和提交行为。在高并发环境下,事务可能因锁等待或死锁而失败。 可通过以下语句查看当前隔离级别: ```sql SELECT @@tx_isolation; ``` 如需调整,可在 MySQL 配置文件中设置: ```ini [mysqld] transaction-isolation = READ-COMMITTED ``` 重启 MySQL 服务后生效。此操作可优化事务并发行为,减少因隔离级别导致的事务失败[^1]。 ### 3.5 多连接与事务绑定问题 Qt 中事务绑定于单个数据库连接。若在事务开启后切换了数据库连接(如使用多个 `QSqlDatabase` 实例),事务将失效。应在事务执行期间确保使用相同的数据库连接实例,并避免在事务过程中执行 `use database` 或切换连接的操作。 例如,以下代码确保事务在单一连接上执行: ```cpp QSqlDatabase db = QSqlDatabase::database(); // 获取默认连接 if (!db.transaction()) { qDebug() << "事务启动失败:" << db.lastError().text(); } ``` 确保事务与连接绑定,可避免因连接切换导致的事务失败[^1]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值