• 国产数据库与Oracle数据库事务差异分析

国产数据库与Oracle数据库事务差异分析

2025-04-27 10:41:15 栏目:宝塔面板 2 阅读

数据库中的ACID是事务的基本特性,而在Oracle等数据库迁移到国产数据库国产中,可能因为不同数据库事务处理机制的不同,在迁移后的业务逻辑处理上存在差异。本文简要介绍了事务的ACID属性、事务的隔离级别、回滚机制和超时机制,并总结SAVEPOINT的使用,以总结。

1、数据库中事务基本概念

事务是数据库中的基本逻辑操作单元,由一组不可分割的数据库操作序列组成,这些操作要么全部成功执行,要么全部失败回滚。其核心目的是确保数据的完整性和一致性,尤其在并发操作或系统故障时维护数据库的可靠状态。

1.1 事务基本属性

ACID是事务的基本特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

  • 原子性:事务中的所有操作必须作为一个整体执行,要么全部执行成功、要么全部失败回滚,不允许出现部分成功的情况。在数据库中通常是通过日志记录(如undo log)来实现回滚操作,若事务执行失败,系统跟进日志撤销已执行的操作。
  • 一致性:事务执行前后,数据库必须保持一致性状态。所有数据必须满足预定义的完整性约束(如主键、外键、唯一性约束等)。即使事务失败,数据库也不能破坏这些规则。在数据库中通过一些约束和检查来确保数据库的完整性约束。
  • 隔离性:多个事务并发执行时,每个事务的操作应与其他事务相互隔离,使得每个事务感觉不到其他事务的存在,最终效果应与事务串行执行的结果一致。数据库中通过锁机制(Locking)或多版本并发控制(MVCC)实现,不同的隔离级别提供不同程度的隔离性。
  • 持久性:事务一旦提交,其对数据库的修改就是永久性的,即使系统发生故障(如断电、崩溃),修改也不会丢失。数据库中通过重做日志(Redo Log)实现持久性。提交事务时,对数据的修改首先写入日志,再异步写入数据库文件中。当数据库崩溃恢复时,通过重放日志恢复数据。

以转账交易为例,通过undo日志实现原子性,确保“扣款”和“存款”两个操作要么全部成功,要么全部失败;一致性是确保转账前后,数据库必须满足业务规则(如余额不为负、总额不变);通过锁机制和MVCC多版本并发控制来实现事务的隔离性,多个并发转账操作互不干扰,结果与串行执行一致;持久性则是一旦转账成功,即使系统崩溃,修改也不会丢失。

BEGIN TRANSACTION;
-- 1. 检查一致性:用户A余额是否足够(一致性)
SELECT balance FROM accounts WHERE user = 'A' FOR UPDATE;
-- 如果余额 < 100,抛出错误并回滚
-- 2. 扣款(原子性)
UPDATE accounts SET balance = balance - 100 WHERE user = 'A';
-- 3. 存款(原子性)
UPDATE accounts SET balance = balance + 100 WHERE user = 'B';
-- 4. 提交(持久性)
COMMIT;

1.2 事务隔离级别

事务隔离级别是数据库事务处理的基础,SQL-92标准定义了4种隔离级别:读未提交(READ UNCOMMITTED)、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)、串行化(SERIALIZABLE)。详见下表:

不同的隔离级别有不同的现象,并有不同的锁和并发机制。隔离级别越高,数据库的并发性能就越差。

1.2.1 脏读/不可重复读/幻读现象

1)脏读A事务读取B事务尚未提交的更改数据,并在这个数据的基础上进行操作,这时候如果事务B回滚,那么A事务读到的数据是不被承认的。

2)不可重复读不可重复读是指在同一个事务中,同一个查询在T1时刻读取一行数据,在T2时刻重新读取这一行数据的时候,发现这一行数据已经发生了修改(被更新或者删除)。假如A在取款事务的过程中,B往该账户转账100,A两次读取的余额发生不一致。

3)幻读幻读是指在同一个事务中,当同一个查询多次执行的时候,由于其它插入操作的事务提交,会导致每次返回不同的结果集。不可重复读和幻读的区别是:前者是指读到了已经提交的事务的更改数据(修改或删除),后者是指读到了其他已经提交事务的新增数据。

1.2.2 行锁模式
  • Share:lock owner和任何并发程序可以read但是不能change locked page或row,并发程序可能获得S-lock、U-lock,也可能没有lock就进行读操作
  • Update:lock owner可read但是不能change locked page或row,但是owner可以将U-lock升级为X-lock这样就可以修改page或row
  • 升级为X-lock这个过程可能会引起其它S-lock的并发进程暂停在那
  • 当lock owner读数据的时候并决定是否需要修改它的时候,U-lock会减少deadlocks的几率
  • Exclusive:只有lock owner才能read或change locked page或row,并发程序只有当程序处于UNCOMMITTED read isolation的时候才能访问数据
  • Lock mode compatibility,见下表

图片

比如说User A对page hold住S-lock,如果User B想对page请求X-lock,则User A的lockmode会拒绝User B的请求。

1.2.3 隔离级别

1)读未提交(Read Uncommitted)读未提交,就是一个事务可以读取另一个未提交事务的数据,也称为脏读。在读数据时候不加锁,写数据时候加行级别的共享锁,提交时释放锁。行级别的共享锁,不会对读产生影响,但是可以防止两个同时的写操作

2)读已提交(Read Committed)读提交,就是一个事务要等另一个事务提交后才能读取它的数据,否则是读取不到另外一个事务的更改的数据。事务读取数据(读到数据的时候)加行级共享S锁,读完释放;事务写数据时候(写操作发生的瞬间)加行级独占X锁,事务结束释放。由于事务写操作加上独占X锁,因此事务写操作时,读操作也不能进行,因此,不能读到事务的未提交数据,避免了脏读的问题。但是由于,读操作的锁加在读上面,而不是加在事务之上,所以,在同一事务的两次读操作之间可以插入其他事务的写操作,所以可能发生不可重复读的问题。

3)可重复读(Repeatable Read)当事务隔离级别为可重复读时,只能读到该事务启动时已经提交的其他事务修改的数据,未提交的数据或在事务启动后其他事务提交的数据是不可见的。对于本事务而言,事务语句可以看到之前的语句做出的修改。事务读取数据在读操作开始的瞬间就加上行级共享S锁,而且在事务结束的时候才释放。但是,由于加的是行级别的锁,仍然可能发生幻读的问题。

4)序列化(Serialization)最严格的隔离级别,强制事务串行执行,使之不可能冲突,从而解决幻读的问题,资源消耗最大。在读操作时,加表级共享锁,事务结束时释放;写操作时候,加表级独占锁,事务结束时释放。在这个级别,可能会导致大量的锁超时和锁竞争现象,实际上也很少用到。

1.2.4 不同数据库隔离级别

不同数据库支持的隔离级别也不同

图片

  • Oracle数据库支持读已提交和序列化,默认隔离级别为Read Committed,通过通过多版本并发控制(MVCC)避免脏读,但存在不可重复读和幻读。
  • MySQL数据库支持四种隔离级别,默认为可重复读,通过MVCC和间隙锁来减少幻读问题。
  • MySQL数据库支持四种隔离级别,默认为Read Committed
  • OceanBase(for Oracle)模式支持读已提交和序列化,默认为Read Committed
  • TiDB支持读已提交、可重复读和序列化,默认为Repeatable Read,其实在TiDB中实现是快照隔离,类似可重复读;
  • GoldenDB兼容MySQL的隔离机制,支持4种隔离级别,但是默认的级别是Read Committed,也是并发和一致性平衡的结果;
  • GaussDB支持Read Committed和Repeatable Read,默认隔离级别是Read Committed

1.3 事务回滚机制

事务的原子性要求事务要么全部执行成功、要么全部执行失败回滚,但是对于Oracle数据库支持语句级的原子性,也就是一个事务中单个语句执行失败,则只会回滚该语句执行的操作,不会导致在当前事务中丢失之前的任何工作。如果需要回滚整个事务,需要处理错误并且主动调用ROLLBACK。这种语句级的回滚对于处理一些长时间运行的批处理任务有用,逻辑上希望能够处理错误,不需要回滚已经完成的所有操作。

图片

不过从Oracle数据库迁移到国产数据库中,大部分数据库在事务的回滚机制上并不支持语句级别的,因此需要通过采用SAVEPOINT保存点的方式。使用SAVEPOINT虽然可以解决语句级别功能上的需求,但是不合理的使用可能引发其它问题。

1.4 事务超时机制

数据库中事务会设置不同的超时机制,防止因为出现等锁而出现无限等待,超过这个时间后会出现等锁超时,事务会回滚。

图片

  • Oracle数据库:默认不会主动终止因行锁等待而阻塞的事务,事务会无限期等待锁释放,需由应用层处理或手动终止,行锁在事务提交或回滚是自动释放;事务默认也无超时设置,但是可以限制会话的空闲时间,超过时间后会断开链接。
  • MySQL数据库:通过 innodb_lock_wait_timeout 控制,默认为50秒。当事务等待锁超过此时间时,会抛出错误;事务中无默认超时时间,但是连接的空闲超时设置wait_timeout,默认8小时。
  • PostgreSQL:通过pg_lock_timeout设置行锁等待超时时间,默认为0无限等待;事务中设置statement_timeout 控制单条SQL执行时间,默认无限制。
  • TiDB:兼容MySQL行锁等待设置;如果是悲观事务,默认TTL(Time-To-Live)为 1小时,超时后自动回滚,另外通过tidb_idle_transaction_timeout 控制空闲事务。
  • OceanBase:MySQL模式下兼容MySQL设置;事务中通过 ob_query_timeout 控制事务单条语句执行时间,默认1800s
  • GoldenDB:兼容MySQL设置
  • GaussDB:参数lockwait_timeout控制单个锁的最长等待时间,当申请的锁等待时间超过设定值时,系统会报错,默认为20min;通过通过 statement_timeout 控制单个语句执行时长,默认0表示不控制。

1.5 如何使用SAVEPOINT

前文提到Oracle数据库中支持语句级别的回滚,在迁移到国产数据库后,为了兼容Oracle数据库这个特性,很多数据库支持SAVEPOINT机制。SAVEPOINT是事务中的一个逻辑标记点,用于标识事务执行到某个特定位置的状态。通过ROLLBACK TO SAVEPOINT可以回滚到该标记点,撤销该点之后的所有操作,但保留该点之前的操作。SAVEPOINT可以提供细粒度的事务控制,避免因整个事务回滚导致的数据丢失。对于一些复杂或长时间运行的事务中,可以分阶段提交或回滚操作。

BEGIN
  INSERT INTO orders (id, amount) VALUES (1, 100);
  SAVEPOINT sp1;  -- 设置保存点sp1
  UPDATE inventory SET stock = stock - 1 WHERE product_id = 101;
  SAVEPOINT sp2;  -- 设置保存点sp2
  -- 假设此处发生错误
  ROLLBACK TO sp1;  -- 回滚到sp1,撤销UPDATE操作
  COMMIT;           -- 提交事务(仅保留INSERT操作)
END;

但是在一个长事务中不规范的使用SAVEPOINT可能会导致实例内存上涨、事务执行时间异常的问题。比如在Oracle数据库中的一个游标查询语句,每1K笔执行一次,迁移到GaussDB之后,URL串中指定了autosave参数,这样在每次游标访问时候都会执行一次SAVEPOINT动作,原来几分钟的任务可能几个小时都执行不完,出现很多SAVEPOINT等待事件,并且数据库实例的内存逐渐上涨。

那么对于一些复杂的业务场景,如何合理的使用SAVEPOINT?

  • 禁止自动设置SAVEPOINT,只在必要时设置SAVEPOINT,如在数据更新、复杂逻辑分支操作上设置;
  • 将长事务拆分为多个小事务,每处理一定的数据后提交,以释放资源;
  • 避免在游标循环中频繁执行SAVEPOINT,改用批量处理或分页查询;
  • 及时回滚或释放无用SAVEPOINT,在逻辑分支完成后,主动回滚到最近的SAVEPOINT并释放资源;
  • 避免嵌套过深的SAVEPOINT:过多的嵌套SAVEPOINT会增加回滚段的复杂度。

参考资料:

  1. https://pigsty.cc/blog/db/oracle-pg-xact/
  2. TiDB中的事务处理机制

本文地址:https://www.yitenyun.com/104.html

搜索文章

Tags

Deepseek 宝塔面板 Linux宝塔 Docker JumpServer JumpServer安装 堡垒机安装 Linux安装JumpServer Windows Windows server net3.5 .NET 安装出错 宝塔面板打不开 宝塔面板无法访问 esxi esxi6 root密码不对 无法登录 web无法登录 Windows宝塔 Mysql重置密码 SSL 堡垒机 跳板机 HTTPS 无法访问宝塔面板 HTTPS加密 修改DNS Centos7如何修改DNS 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 scp Linux的scp怎么用 scp上传 scp下载 scp命令 工具 sqlmock SQL 防火墙 服务器 黑客 Serverless 无服务器 语言 网络架构 网络配置 IT运维 MySQL B+Tree ID 字段 Linux 安全 List 类型 Redis 速度 服务器中毒 聚簇 非聚簇 索引 频繁 Codis InnoDB LRU 数据库 Oracle 处理机制 AI 助手 RocketMQ 长轮询 配置 数据库锁 HexHub SQLite Redka SQLite-Web 数据库管理工具 IT MVCC 事务隔离 Caffeine CP Rsync 同城 双活 序列 核心机制 数据备份 缓存 MySQL 9.3 架构 部署 开发 API FastAPI 双引擎 优化 开源 PostgreSQL 存储引擎 sftp 服务器 参数 QPS 高并发 虚拟服务器 虚拟机 内存 万能公式 mini-redis INCR指令 Web 应用 异步数据库 MongoDB 数据结构 悲观锁 乐观锁 StarRocks 数据仓库 openHalo OB 单机版 Doris SeaTunnel 数据集成工具 数据 业务 Ftp 监控 prometheus Alert 单线程 线程 不宕机 分库 分表 Spring 动态查询 Python Web Calcite 电商系统 信息化 智能运维 分布式架构 分布式锁​ dbt 数据转换工具 容器 响应模型 缓存方案 缓存架构 缓存穿透 SpringAI Milvus 向量数据库 原子性 云原生 线上 库存 预扣 Entity Netstat Linux 服务器 端口 对象 Testcloud 云端自动化