当前位置: 首页 > news >正文

MySQL事务——博主总结

一、博主总结(首先要会的先总结一下方便有基础的同学食用)

在数据库中,事务是关键概念,如同 “坚固盾牌”,保障数据的一致性、完整性与可靠性。以网上银行转账为例,从账户 A 向账户 B 转 500 元,涉及从账户 A 扣钱和向账户 B 加钱两步。若转账时数据库服务崩溃等,会致账户 A 钱扣了但账户 B 未收到,违背金融交易逻辑。电商系统用户下单也类似,涉及库存、订单、余额等多操作,若不能整体成功执行,会出现数据不一致。事务像 “魔法盒子”,将系列数据库操作封装,要么全成功,要么全失败回滚,不存在部分执行情况。接下来将探索 MySQL 中事务的奥秘。

梳理总结

  • 事务
    • MySQL事务的四大特性说一下?
      • 四大特性是什么?
        • 四个特性分别是原子性、一致性、隔离性和持久性(ACID)。
        • 原子性保证事务中的操作要么全部执行、要么全部失败;
        • 一致性保证数据从事务开始前的一个一致状态转移到结束后的另外一个一致状态;
        • 隔离性保证并发事务之间互不干扰;
        • 持久性保证事务提交后数据不会丢失。
      • ACID如何保证?
        • 原子性是通过 undo log(回滚日志) 来保证的。事务对数据进行修改前,会记录一份快照到 Undo Log,如果事务中有任何一步执行失败,系统会读取 Undo Log 将所有操作回滚,恢复到事务开始前的状态,从而保证事务要么全部成功,要么全部失败。
        • 一致性则是通过持久性+原子性+隔离性来保证;
        • 隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;
          • MVCC:主要用来优化读操作,通过保存数据的历史版本,让读操作不需要加锁就能直接读取快照,提高读的并发性能。不同的隔离级别对应不同的实现策略,比如说在可重复读隔离级别下,事务第一次查询时会生成一个 Read View,之后所有读操作都复用这个视图,保证多次读取的结果一致。
          • 锁机制:
        • 持久性主要由预写 Redo Log、双写机制、两阶段提交以及 Checkpoint 刷盘机制共同保证。
          • Redo Log:当事务提交时,MySQL 会先将事务的修改操作写入 Redo Log,并强制刷盘,然后再将内存中的数据页刷入磁盘。这样即使系统崩溃,重启后也能通过 Redo Log 重放恢复数据。
          • 双写机制:脏盘刷页时,先将数据页写入到一个双写缓冲区中,2M 的连续空间,然后再将其写入到磁盘的实际位置。

          • 两阶段提交:在涉及主从复制时,MySQL 通过两阶段提交保证 Redo Log 和 Binlog 的一致性:第一阶段,写入 Redo Log 并标记为 prepare 状态;第二阶段,写入 Binlog 再提交 Redo Log 为 commit 状态。
          • Checkpoint 刷盘机制:由于 Redo Log 的容量有限,Checkpoint 机制会定期将内存中的脏页刷到磁盘,这样能减少崩溃恢复时需要处理的 Redo Log 数量。
    • 事务的隔离级别?
      • 事务的隔离级别有哪些?
        • MySQL 支持四种隔离级别,分别是:读未提交、读已提交、可重复读和串行化。
      • 这四种隔离级别具体是如何实现的呢?
        • 对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
        • 对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
        • 对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View来实现的,它们的区别在于创建 Read View 的时机不同,「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。
      • mysql默认级别是什么?可重复读隔离级别
    • mysql并发相关问题?
      • 在同时处理多个事务的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题。
      • mysql的是怎么解决并发问题的?
        • 锁机制:Mysql提供了多种锁机制来保证数据的一致性,包括行级锁、表级锁、页级锁等。通过锁机制,可以在读写操作时对数据进行加锁,确保同时只有一个操作能够访问或修改数据。
        • 事务隔离级别:通过设置合适的事务隔离级别,可以在多个事务并发执行时,控制事务之间的隔离程度,以避免数据不一致的问题。
          • 比如说在读未提交的隔离级别下,会出现脏读现象:一个事务C 读取了事务B 尚未提交的修改数据。如果事务B 最终回滚,事务C 读取的数据就是无效的“脏数据”。
          • 通过升级隔离级别为读已提交可以解决脏读的问题。
          • 但会出现不可重复读的问题:事务B 第一次读取某行数据值为X,期间事务C修改该数据为Y并提交,事务B 再次读取时发现值变为Y,导致两次读取结果不一致。
          • 可以通过升级隔离级别为可重复读来解决不可重复读的问题。
          • 但可重复读级别下仍然会出现幻读的问题:事务B 第一次查询获得 2条数据,事务C 新增 1条数据并提交后,事务B 再次查询时仍然为 2 条数据,但可以更新新增的数据,再次查询时就发现有 3 条数据了。
          • 可以通过升级隔离级别为串行化来解决幻读的问题。
        • MVCC(多版本并发控制):Mysql使用MVCC来管理并发访问,它通过在数据库中保存不同版本的数据来实现不同事务之间的隔离。在读取数据时,Mysql会根据事务的隔离级别来选择合适的数据版本,从而保证数据的一致性。
    • 不可重复读?之前读到的数据和现在读到的数据出现内容不一致。
    • 幻读?之前读到的数据和现在读到的数据出现范围(数量)不一致。
    • MVCC实现原理?undolog链+read view。

二、MySQL 事务基础

(一)事务的定义

在 MySQL 中,事务是一组不可分割的数据库操作集合,这些操作要么全部成功执行,使数据库状态发生预期的改变;要么全部失败回滚,数据库状态保持不变,就像这组操作从未发生过一样 。事务的这种特性确保了数据的一致性和完整性,避免因部分操作成功、部分操作失败而导致的数据不一致问题。

以银行转账为例,从账户 A 向账户 B 转账 1000 元,这个过程涉及两个关键操作:从账户 A 中减去 1000 元,以及向账户 B 中增加 1000 元。这两个操作必须作为一个事务来处理,如果只执行了从账户 A 扣款的操作,而向账户 B 的存款操作失败,就会导致数据不一致,用户的资金安全也无法得到保障。只有当这两个操作都成功完成,或者都未执行时,才能保证数据的准确性和一致性。

(二)事务操作语句

在 MySQL 中,主要通过以下几个语句来对事务进行控制:

  • 开启事务:可以使用START TRANSACTION或者BEGIN语句来开启一个事务。这两个语句的功能基本相同,使用START TRANSACTION更为标准,但BEGIN更为简洁,在实际应用中二者都很常用。当执行这两个语句中的任意一个后,后续的数据库操作都将被视为事务的一部分,直到遇到COMMIT(提交)或ROLLBACK(回滚)语句。比如:

START TRANSACTION;

-- 或者

BEGIN;

  • 提交事务:使用COMMIT语句来提交事务。当事务中的所有操作都成功执行,并且你希望将这些操作对数据库所做的修改永久保存时,就需要执行COMMIT语句 。一旦执行了COMMIT,事务中的所有操作就会被确认,对数据库的修改将持久化存储,其他事务也能够看到这些修改后的结果。例如:

START TRANSACTION;

-- 一系列数据库操作,如插入、更新、删除等

UPDATE accounts SET balance = balance - 1000 WHERE account_id = 'A';

UPDATE accounts SET balance = balance + 1000 WHERE account_id = 'B';

COMMIT;

  • 回滚事务:使用ROLLBACK语句来回滚事务。当事务执行过程中出现错误,或者你主动决定取消事务中的所有操作时,就可以执行ROLLBACK语句 。执行ROLLBACK后,事务中所有已经执行但尚未提交的操作将被撤销,数据库状态将恢复到事务开始前的状态,就好像这些操作从未发生过一样。比如:

START TRANSACTION;

-- 假设在执行以下操作时出现错误

UPDATE accounts SET balance = balance - 1000 WHERE account_id = 'A';

-- 发现错误后回滚事务

ROLLBACK;

通过这几个基本的事务操作语句,我们就能够在 MySQL 中灵活地控制事务,确保数据库操作的原子性、一致性、隔离性和持久性 ,也就是事务的 ACID 特性,这是保证数据库数据完整性和可靠性的关键所在。接下来,我们将深入探讨事务的 ACID 特性以及事务隔离级别等重要概念。

三、事务的四大特性(ACID)

事务具有四大特性,通常用 ACID 来表示,分别是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability) 。这四大特性是事务的核心,它们共同保证了数据库操作的正确性和可靠性,接下来我们将逐一详细介绍。

(一)原子性(Atomicity)

原子性确保事务中的所有操作要么全部执行成功,要么全部不执行,就像一个不可分割的原子一样 。在 MySQL 中,这一特性通过 undo log 来实现。当事务执行过程中出现错误需要回滚时,undo log 会记录事务执行过程中对数据的修改操作,然后根据这些记录将数据恢复到事务开始前的状态。

以银行转账事务为例,从账户 A 向账户 B 转账 1000 元,这个事务包含两个操作:从账户 A 中减去 1000 元,以及向账户 B 中增加 1000 元。在执行这个事务时,MySQL 会先开启一个事务,然后依次执行这两个操作。如果在执行过程中,比如从账户 A 扣款成功后,向账户 B 存款时出现错误,MySQL 会利用 undo log 中记录的从账户 A 扣款的操作,将账户 A 的余额恢复到原来的状态,从而保证整个事务的原子性,避免出现账户 A 钱少了但账户 B 却没收到钱的情况。

(二)一致性(Consistency)

一致性是指事务执行前后,数据库的完整性约束没有被破坏,数据从一个合法的状态转换到另一个合法的状态 。它是事务的最终目的,原子性、隔离性和持久性都是为了保证一致性而存在的。一致性的实现不仅依赖于数据库本身的约束机制(如主键约束、外键约束、唯一性约束等),还需要应用层面的业务逻辑来保障。

例如,在一个电商系统中,商品库存和订单是相关联的。当用户下单购买商品时,订单表中会插入一条订单记录,同时商品库存表中相应商品的库存数量会减少。在这个事务中,数据库的主键约束确保订单表和库存表中的每条记录都有唯一标识;外键约束保证订单表中的商品 ID 与库存表中的商品 ID 关联正确;而业务逻辑则要求订单中的商品数量不能超过库存数量,否则事务回滚,以保证数据的一致性。如果在事务执行过程中,因为某种原因导致库存数量减少了,但订单记录却未插入成功,就会破坏数据的一致性。

(三)隔离性(Isolation)

隔离性保证多个并发事务之间相互隔离,一个事务的执行不会被其他事务干扰 ,每个事务都感觉不到其他事务的存在。在 MySQL 中,通过锁机制和 MVCC(多版本并发控制)来实现隔离性。不同的隔离级别对并发事务的隔离程度不同,从低到高依次为读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable) 。

  • 读未提交(Read Uncommitted):这是最低的隔离级别,在这个级别下,一个事务可以读取到其他事务未提交的数据,这种现象被称为 “脏读”。例如,事务 A 修改了一条数据但还未提交,事务 B 此时读取到了这条被修改但未提交的数据,如果事务 A 最终回滚,那么事务 B 读取到的数据就是无效的 “脏数据”。
  • 读提交(Read Committed):在该级别下,一个事务只能读取到其他事务已经提交的数据,避免了脏读问题,但可能会出现 “不可重复读” 现象。即一个事务在两次读取同一数据期间,另一个事务对该数据进行了修改并提交,导致第一个事务两次读取的结果不一致。
  • 可重复读(Repeatable Read):MySQL 的默认隔离级别,它保证在同一个事务中多次读取同一数据时,得到的结果是一致的,解决了不可重复读问题,但在某些情况下可能会出现 “幻读”。幻读是指在一个事务中,按照相同的查询条件重新查询数据时,发现其他事务插入了满足该条件的新数据,导致结果集中出现了之前没有的 “幻影” 数据。
  • 串行化(Serializable):这是最高的隔离级别,事务之间完全串行执行,避免了脏读、不可重复读和幻读问题,但会严重降低并发性能,因为所有事务必须依次排队执行。

(四)持久性(Durability)

持久性意味着一旦事务被提交,它对数据库所做的修改就会永久保存下来,即使系统发生故障(如服务器崩溃、断电等)也不会丢失 。在 MySQL 中,持久性主要通过 redo log(重做日志)来实现。当事务提交时,InnoDB 存储引擎会将事务对数据的修改记录到 redo log 中,redo log 是顺序写入磁盘的,具有很高的写入效率。

当数据库发生故障重启时,InnoDB 会根据 redo log 中的记录,将未写入磁盘的数据页重新写入,从而保证已提交事务的持久性。例如,在一个转账事务中,当事务提交后,即使此时服务器突然断电,在服务器恢复后,MySQL 也能通过 redo log 将转账操作对账户余额的修改持久化到磁盘中,确保数据不会丢失 。

四、并发事务问题与隔离级别

(一)并发事务问题

在多事务并发执行的环境中,如果不对事务进行有效的隔离,就可能出现一些数据一致性问题,主要包括脏读、不可重复读和幻读 。

脏读(Dirty Read):指一个事务读取到了另一个事务未提交的数据 。这种情况会导致数据的不一致性,因为未提交的数据可能会被回滚,如果一个事务基于这些未提交的数据做出决策,那么当数据回滚时,该事务的决策就可能是错误的。例如,在电商系统中,事务 A 对商品的库存数量进行了修改,但还未提交事务,此时事务 B 读取到了被事务 A 修改后的库存数量,并以此为依据进行了订单处理。如果事务 A 随后回滚了对库存的修改,那么事务 B 处理订单时依据的库存数据就是错误的,可能导致超卖等问题。

不可重复读(Non - Repeatable Read):在一个事务内,多次读取同一数据时,得到的结果不一致 。这是因为在两次读取之间,另一个事务对该数据进行了修改并提交。例如,在财务系统中,事务 A 在计算某个账户的余额时,第一次读取余额为 1000 元,在事务 A 还未结束时,事务 B 对该账户进行了一笔转账操作并提交,此时事务 A 再次读取该账户余额,得到的结果可能就变为了 800 元。这就导致事务 A 在同一个事务内对同一数据的读取结果不一致,可能会影响到后续的业务逻辑,比如财务报表的准确性。

幻读(Phantom Read):当一个事务按照相同的查询条件多次查询数据时,在两次查询之间,另一个事务插入或删除了符合查询条件的数据,导致该事务前后两次查询得到的结果集不一致,好像出现了 “幻影” 数据 。例如,在一个电商订单管理系统中,事务 A 查询所有金额大于 1000 元的订单,第一次查询得到了 5 条订单记录。在事务 A 未结束时,事务 B 插入了一条金额大于 1000 元的新订单并提交。当事务 A 再次执行相同的查询时,就会得到 6 条订单记录,多出来的这条新订单就像是 “幻影” 一样,这就是幻读现象。幻读可能会对一些统计分析类的事务造成影响,比如统计订单金额总和时,由于幻读导致数据不准确。

(二)事务隔离级别

为了解决并发事务带来的问题,MySQL 提供了四种事务隔离级别,从低到高分别是读未提交(READ UNCOMMITTED )、读提交(READ COMMITTED )、可重复读(REPEATABLE READ )和串行化(SERIALIZABLE ) 。

  • 读未提交(READ UNCOMMITTED ):这是最低的隔离级别,在这种级别下,一个事务可以读取到其他事务未提交的数据,这就会导致脏读问题的发生 。由于它对事务之间的隔离性几乎没有任何保障,所以在实际应用中很少使用。例如,在一个银行转账事务中,事务 A 从账户 A 向账户 B 转账 1000 元,在事务 A 未提交时,事务 B 可以读取到账户 A 已经减少 1000 元后的余额,如果事务 A 最终回滚,那么事务 B 读取到的就是错误的 “脏数据”。虽然读未提交级别下事务执行的并发性能很高,因为几乎没有任何锁机制和事务隔离限制,但由于其严重的数据一致性问题,在对数据准确性要求较高的场景中,如金融、财务等领域,是绝对不能使用的 。
  • 读提交(READ COMMITTED ):在该级别下,一个事务只能读取到其他事务已经提交的数据,避免了脏读问题 。但在这种隔离级别下,仍然可能出现不可重复读的情况。因为一个事务在两次读取同一数据期间,如果另一个事务对该数据进行了修改并提交,那么第一个事务两次读取的结果就会不一致。例如,在一个员工信息管理系统中,事务 A 在查询员工的薪资信息时,第一次读取到员工张三的薪资为 5000 元,在事务 A 还未结束时,事务 B 对张三的薪资进行了调整并提交,当事务 A 再次读取张三的薪资时,得到的结果就变为了 5500 元,这就导致了不可重复读问题。读提交级别在实际应用中较为常见,尤其是在一些对数据一致性要求不是特别严格,同时又希望有较高并发性能的场景中,如一些普通的电商订单查询、商品展示等业务场景 。它通过在读取数据时加共享锁,在数据提交后释放锁的机制,来保证事务只能读取到已提交的数据,相较于读未提交级别,在一定程度上提高了数据的一致性,但也增加了一些锁的开销。
  • 可重复读(REPEATABLE READ ):这是 MySQL 的默认隔离级别,它保证在同一个事务中多次读取同一数据时,得到的结果是一致的,解决了不可重复读问题 。在可重复读级别下,事务在开始时会创建一个数据快照,后续的读操作都基于这个快照进行,所以即使其他事务对数据进行了修改并提交,本事务读取到的数据也不会改变。例如,在一个电商库存管理系统中,事务 A 在查询某商品的库存数量时,第一次读取到库存为 100 件,在事务 A 未结束期间,事务 B 对该商品的库存进行了修改并提交,当事务 A 再次查询该商品库存时,仍然会读取到 100 件,保证了数据读取的一致性。然而,在可重复读级别下,虽然通过 MVCC(多版本并发控制)和间隙锁等机制大大减少了幻读的发生概率,但在某些特殊情况下(如使用范围查询和插入操作结合时),仍然可能出现幻读现象 。不过总体来说,可重复读级别在数据一致性和并发性能之间找到了一个较好的平衡点,适用于大多数对数据一致性有较高要求,同时又需要一定并发性能的业务场景,如电商的订单处理、库存管理等核心业务模块 。
  • 串行化(SERIALIZABLE ):这是最高的隔离级别,事务之间完全串行执行,即一个事务执行完后,另一个事务才开始执行 。在这种级别下,所有的并发事务问题(脏读、不可重复读和幻读)都不会出现,因为不存在并发执行的情况。例如,在一个对数据一致性要求极高的金融交易系统中,涉及到资金的转账、结算等关键操作时,可以使用串行化隔离级别来确保数据的绝对准确和一致性。但由于所有事务必须依次排队执行,这会严重降低系统的并发性能,在高并发场景下,系统的响应时间会变得很长,吞吐量也会大幅下降 。所以,串行化隔离级别一般只在对数据一致性要求极高,且并发量较小的场景中使用,如一些涉及核心金融业务、关键数据处理的场景,而在大多数互联网高并发应用场景中,由于对系统性能和并发处理能力要求较高,很少会选择串行化隔离级别 。

不同的事务隔离级别对并发事务问题的解决程度不同,同时也会对系统的性能产生不同的影响 。在实际应用中,我们需要根据业务场景的具体需求,综合考虑数据一致性和并发性能等因素,选择合适的事务隔离级别 。例如,对于一些简单的查询类业务,对数据一致性要求不是特别严格,可以选择读提交级别以提高并发性能;而对于一些涉及核心业务数据处理,对数据一致性要求极高的场景,则可能需要选择可重复读甚至串行化级别 。

五、事务原理深入剖析

(一)undo log

undo log 主要用于事务回滚,是保证事务原子性的关键机制 。当事务执行过程中需要回滚时,undo log 会根据记录的信息将数据恢复到事务开始前的状态 。例如,在一个事务中执行了一条UPDATE语句修改了某条记录,undo log 会记录修改前的原始数据。如果事务因为某些原因(如出现错误、用户主动回滚等)需要回滚,就可以根据 undo log 中记录的原始数据将修改后的记录恢复到原来的状态,从而保证事务的原子性,即事务中的所有操作要么全部成功执行,要么全部不执行。

在 MySQL 的 InnoDB 存储引擎中,undo log 分为Insert Undo Log和Update Undo Log 。Insert Undo Log用于INSERT操作的回滚,当插入一条记录时,会将该记录的主键值等信息记录到Insert Undo Log中 。如果事务需要回滚,只需要根据记录的主键值删除这条插入的记录即可 。例如:

START TRANSACTION;

INSERT INTO users (user_id, username, password) VALUES (1, 'testuser', '123456');

-- 假设此时需要回滚事务

ROLLBACK;

在这个例子中,插入操作执行后,Insert Undo Log记录了插入记录的主键user_id为 1 等信息。当执行ROLLBACK时,根据Insert Undo Log中的记录,将刚刚插入的这条记录删除,实现事务的回滚。

Update Undo Log则用于UPDATE和DELETE操作的回滚 。对于UPDATE操作,它会记录修改前的旧数据;对于DELETE操作,它会记录被删除的完整数据 。当需要回滚时,根据Update Undo Log中的记录,将数据恢复到修改前的状态或重新插入被删除的数据 。例如:

START TRANSACTION;

UPDATE users SET password = 'newpassword' WHERE user_id = 1;

-- 假设此时需要回滚事务

ROLLBACK;

在这个UPDATE操作的事务中,Update Undo Log记录了user_id为 1 的用户修改前的密码123456等旧数据信息。当执行ROLLBACK时,根据Update Undo Log中的记录,将user_id为 1 的用户密码更新回123456,完成事务的回滚。

此外,undo log 在 MVCC(多版本并发控制)中也起着重要作用 。在 MVCC 机制下,当一个事务对数据进行修改时,会生成一个新的版本,并将旧版本的数据记录到 undo log 中 。其他事务在进行快照读(即普通的SELECT操作)时,如果读取的行被锁定,就可以从 undo log 中获取该行数据在之前版本的状态,实现非阻塞读,从而提高数据库的并发性能 。例如,事务 A 正在修改某条记录,事务 B 此时进行快照读,由于事务 A 还未提交,事务 B 无法直接读取到事务 A 修改后的最新数据,但可以通过 undo log 读取到该记录修改前的版本数据,避免了事务 B 的等待,提升了并发处理能力。

(二)redo log

redo log 是保证事务持久性的关键机制 。在 MySQL 中,当数据进行修改时,并不是直接将修改后的数据写入磁盘,而是先将修改操作记录到 redo log buffer(重做日志缓冲区,位于内存中),同时也会修改内存中的数据 。当事务执行COMMIT操作提交时,会先将 redo log buffer 中的内容刷新到磁盘上的 redo log 文件中 ,这个过程称为 “刷盘” 。之后,在适当的时机,内存中被修改的数据(即脏页)才会被刷新到磁盘中 。通过这种方式,即使在事务提交后,系统发生崩溃、断电等故障,只要 redo log 文件存在,在系统重启时,MySQL 就可以根据 redo log 中的记录,将未写入磁盘的数据页重新写入,从而保证已提交事务的持久性,即事务对数据库的修改会永久保存下来 。

例如,在一个转账事务中,从账户 A 向账户 B 转账 100 元 。首先,在内存中对账户 A 和账户 B 的余额进行修改,同时将这些修改操作记录到 redo log buffer 中 。当事务提交时,先将 redo log buffer 中的内容写入 redo log 文件 。此时,即使系统突然崩溃,在重启后,MySQL 可以通过 redo log 文件中的记录,将账户 A 和账户 B 的余额修改操作重新应用到磁盘数据上,确保转账事务的持久性 。之后,内存中修改后的账户数据会在合适的时机被刷新到磁盘 。

redo log 采用顺序写入的方式,相较于直接将数据随机写入磁盘,大大提高了写入性能 。因为磁盘的随机写操作需要频繁地移动磁头,而顺序写操作则可以连续地写入数据,减少了磁头的移动次数,从而提升了写入速度 。这也是 MySQL 使用 redo log 来保证事务持久性的一个重要原因 。同时,redo log 文件通常是循环使用的,当一个 redo log 文件写满后,会切换到下一个文件继续写入 ,通过这种方式可以有效地利用磁盘空间 。

六、事务使用案例与最佳实践

(一)实际案例演示

以电商下单扣库存为例,在电商系统中,当用户下单购买商品时,需要同时进行多个数据库操作,包括插入订单记录、扣减商品库存等。这些操作必须作为一个事务来处理,以确保数据的一致性。

假设我们有两个表,orders(订单表)和products(商品表) ,orders表包含order_id、user_id、product_id、order_amount等字段 ,products表包含product_id、product_name、stock等字段 。以下是使用 Java 和 JDBC 实现电商下单扣库存的事务代码示例:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;public class OrderService {private static final String URL = "jdbc:mysql://localhost:3306/ecommerce";private static final String USER = "root";private static final String PASSWORD = "password";public void placeOrder(int userId, int productId, int orderAmount) {try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) {conn.setAutoCommit(false);// 创建订单String insertOrderSql = "INSERT INTO orders (user_id, product_id, order_amount) VALUES (?,?,?)";try (PreparedStatement insertOrderStmt = conn.prepareStatement(insertOrderSql)) {insertOrderStmt.setInt(1, userId);insertOrderStmt.setInt(2, productId);insertOrderStmt.setInt(3, orderAmount);insertOrderStmt.executeUpdate();}// 更新库存String updateStockSql = "UPDATE products SET stock = stock -? WHERE product_id =? AND stock >=?";try (PreparedStatement updateStockStmt = conn.prepareStatement(updateStockSql)) {updateStockStmt.setInt(1, orderAmount);updateStockStmt.setInt(2, productId);updateStockStmt.setInt(3, orderAmount);if (updateStockStmt.executeUpdate() == 0) {conn.rollback();System.out.println("库存不足,下单失败");return;}}conn.commit();System.out.println("下单成功");} catch (SQLException e) {e.printStackTrace();try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) {conn.rollback();} catch (SQLException ex) {ex.printStackTrace();}}}
}

在上述代码中,首先通过conn.setAutoCommit(false)开启事务 ,然后依次执行插入订单记录和扣减商品库存的操作。如果在扣减库存时发现库存不足(updateStockStmt.executeUpdate()返回 0),则通过conn.rollback()回滚事务 ,订单插入操作也会被撤销,保证数据的一致性。如果所有操作都成功执行,则通过conn.commit()提交事务 ,将订单插入和库存扣减的结果持久化到数据库中 。

(二)事务使用注意事项

在使用事务时,有以下几个重要的注意事项:

  • 避免长时间运行操作:事务中应尽量避免包含长时间运行的操作,如复杂的计算、文件读写、网络请求等 。因为长时间运行的事务会占用数据库资源,可能导致其他事务等待,降低系统的并发性能。同时,长时间运行的事务还会增加死锁的风险。例如,在一个事务中进行大量的数据计算,会使事务长时间持有锁,其他事务无法访问被锁定的数据,从而造成阻塞。如果可能,应将长时间运行的操作放在事务之外执行,或者将大事务拆分成多个小事务,每个小事务执行较短时间的操作并及时提交。
  • 合理设置事务隔离级别:根据业务场景的需求,合理选择事务隔离级别 。不同的隔离级别对数据一致性和并发性能有不同的影响。如果业务对数据一致性要求极高,不允许出现任何并发数据访问问题,如金融交易系统,可以选择串行化隔离级别;但如果业务对并发性能要求较高,且可以容忍一定程度的不可重复读,如一些统计数据的读取,可以考虑将隔离级别设置为读提交 。在 MySQL 中,默认的隔离级别是可重复读,在大多数情况下能够满足业务需求,但在某些特殊场景下,可能需要根据实际情况进行调整 。同时,要注意不同隔离级别可能带来的并发事务问题,如脏读、不可重复读和幻读 ,并采取相应的措施来避免这些问题。
  • 处理事务回滚和异常:在事务执行过程中,要正确处理可能出现的异常和回滚情况 。当事务中某个操作失败时,应及时回滚事务,以确保数据的一致性。例如,在上述电商下单扣库存的例子中,如果扣减库存失败,就需要回滚订单插入操作。同时,要捕获并处理事务操作过程中抛出的异常,避免异常导致程序崩溃。在捕获异常后,可以根据具体情况进行日志记录、错误提示等操作,以便于调试和排查问题。此外,还可以在事务回滚后,根据业务需求进行一些补偿操作,如发送通知给用户订单下单失败等 。

相关文章:

  • pycharm最近遇到的一些问题
  • 理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
  • rapidocr v3.1.0发布
  • HDFS 3.4.1 集成Kerberos 实现账户认证
  • 6月10日星期二今日早报简报微语报早读
  • 用纯.NET开发并制作一个智能桌面机器人(五):使用.NET为树莓派开发Wifi配网功能
  • Unit 2 训练你的第一个深度强化学习智能体 LunarLander-v3
  • 慢接口优化万能公式-适合所有系统
  • 1.2 git使用
  • SIP协议之NACK(Negative Acknowledgement)
  • LLMs 系列实操科普(3)
  • 智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
  • 业财融合怎么做?如何把握业务与财务的边界?
  • crackme008
  • Unity | AmplifyShaderEditor插件基础(第八集:噪声波动shader)
  • Siri在WWDC中的缺席显得格外刺眼
  • day50python打卡
  • 通道注意力机制
  • spring jms使用
  • 上位机开发:C# 读写 PLC 数据块数据
  • 做图素材网站开哪个vip好/连云港网站seo
  • 传奇动态网站怎么做/缅甸在线今日新闻
  • 网站建设有哪三部/如何在互联网推广自己的产品
  • 网站建设公司哪家好智搜宝/网络推广的公司更可靠
  • 购买主机可以做网站吗/长春网站建设解决方案
  • 安徽网站推广营销设计/seo整站优化服务