事务报错没有显示回滚导致DDL阻塞引发的问题

在业务开发过程中,显示的开启事务并且在事务处理过程中对不同的情况进行显示的COMMIT或ROLLBACK,这是一个完整数据库事务处理的闭环过程。

在这里插入图片描述

这种在应用开发逻辑层面去handle的事务执行的结果,既确保了事务操作的数据完整性,又遵循了业务处理逻辑。所以显示的提交或回滚事务也是开发规范中的要求,但是也有一些存量的业务系统或开发人员并不能严格按照这一规范执行,进而在实际生产过程中引发故障。这里介绍一个因为开启事务后未显示的回滚导致DDL阻塞进而引发的问题。

应用系统使用的是MySQL生态的数据库,业务使用的是分区表,业务在处理时候因为当日的分区没有创建导致插入报错,应用逻辑上每日又有对表新增分区的操作,结果是事务没有显示回滚导致新增表分区的DDL阻塞,进而又引发后续的问题。

1、MySQL数据库故障模拟
1.1 创建分区表并插入数据

登录mysql数据库并创建分区表

CREATE TABLE tt1 (  
    id int NOT NULL, 
    sdate date NOT NULL,  
    c1 varchar(4) NOT NULL,  
    PRIMARY KEY (id, sdate)  
)  
PARTITION BY RANGE columns(sdate) (  
    PARTITION p20240524 VALUES LESS THAN ('2024-05-25'),  
    PARTITION p20240525 VALUES LESS THAN ('2024-05-26')
);
1.2 显示的开启事务并插入数据
mysql> begin;
mysql> select * from tango.tt1;
+----+------------+-----+
| id | sdate      | c1  |
+----+------------+-----+
|  1 | 2024-05-25 | aaa |
+----+------------+-----+
1 row in set (0.00 sec)

insert into tt1 values(1,'2024-05-25','aaa');
mysql> insert into tt1 values(3,'2024-05-27','ccc');
ERROR 1526 (HY000): Table has no partition for value from column_list

数据库执行报错提示插入的记录分区不存在。

1.3 查看数据库表中锁和事务的状态
mysql> select * from performance_schema.metadata_locks where object_name='tt1';
+-------------+---------------+-------------+-------------+-----------------------+--------------+---------------+-------------+-------------------+-----------------+----------------+
| OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | COLUMN_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE    | LOCK_DURATION | LOCK_STATUS | SOURCE            | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+---------------+-------------+-------------+-----------------------+--------------+---------------+-------------+-------------------+-----------------+----------------+
| TABLE       | tango         | tt1         | NULL        |       140712994313232 | SHARED_READ  | TRANSACTION   | GRANTED     | sql_parse.cc:5768 |              85 |             24 |
| TABLE       | tango         | tt1         | NULL        |       140712994947616 | SHARED_WRITE | TRANSACTION   | GRANTED     | sql_parse.cc:5768 |              85 |             25 |
+-------------+---------------+-------------+-------------+-----------------------+--------------+---------------+-------------+-------------------+-----------------+----------------+
2 rows in set (0.00 sec)

可以看到表持有SHARED_READ和SHARED_WRITE锁,并不因为事务执行失败而释放,这也是mysql系数据库内核机制,事务报错后数据库层面并没有执行rollback操作,而是由应用自己决定是rollback还是commit。

1.4 其它业务执行新增分区的DDL操作
mysql> ALTER table tt1 ADD PARTITION ( PARTITION p20240526 VALUES LESS THAN ('2024-05-27') );

此时这个DDL操作会hang住,查看表的元数据锁情况

mysql> select * from performance_schema.metadata_locks where object_name='tt1';
+-------------+---------------+-------------+-------------+-----------------------+-------------------+---------------+-------------+-------------------+-----------------+----------------+
| OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | COLUMN_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE         | LOCK_DURATION | LOCK_STATUS | SOURCE            | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+---------------+-------------+-------------+-----------------------+-------------------+---------------+-------------+-------------------+-----------------+----------------+
| TABLE       | tango         | tt1         | NULL        |       140712801139968 | SHARED_READ       | TRANSACTION   | GRANTED     | sql_parse.cc:5768 |             123 |             21 |
| TABLE       | tango         | tt1         | NULL        |       140712793308528 | SHARED_WRITE      | TRANSACTION   | GRANTED     | sql_parse.cc:5768 |             123 |             22 |
| TABLE       | tango         | tt1         | NULL        |       140712926580592 | SHARED_UPGRADABLE | TRANSACTION   | GRANTED     | sql_parse.cc:5768 |             121 |             20 |
| TABLE       | tango         | tt1         | NULL        |       140712928177104 | EXCLUSIVE         | TRANSACTION   | PENDING     | mdl.cc:3753       |             121 |             20 |
+-------------+---------------+-------------+-------------+-----------------------+-------------------+---------------+-------------+-------------------+-----------------+----------------+
5 rows in set (0.00 sec)

可以看到一个pending状态的锁状态,查看对应的SQL语句,知道是新增分区的DDL操作。

mysql> select THREAD_ID,EVENT_ID,EVENT_NAME,TIMER_START,TIMER_END,TIMER_WAIT,LOCK_TIME,SQL_TEXT,STATEMENT_ID from events_statements_current where thread_id=121;
+-----------+----------+---------------------------+------------------+------------------+----------------+-----------+---------------------------------------------------------------------------------------+--------------+
| THREAD_ID | EVENT_ID | EVENT_NAME                | TIMER_START      | TIMER_END        | TIMER_WAIT     | LOCK_TIME | SQL_TEXT                                                                              | STATEMENT_ID |
+-----------+----------+---------------------------+------------------+------------------+----------------+-----------+---------------------------------------------------------------------------------------+--------------+
|       121 |       20 | statement/sql/alter_table | 2670208499587000 | 2687425357664000 | 17216858077000 | 246000000 | ALTER table tt1 ADD PARTITION ( PARTITION p20240526 VALUES LESS THAN ('2024-05-27') ) |        32613 |
+-----------+----------+---------------------------+------------------+------------------+----------------+-----------+---------------------------------------------------------------------------------------+--------------+
1 row in set (0.00 sec)

这里的DDL操作,在mysql数据库中通过参数lock_wait_timeout控制DDL等待超时时间,超过该时间DDL会报错。默认该参数配置为31536000s,实际生产业务系统会设置30~60s,一些核心业务系统会设置为5s。但是在DDL阻塞期间,也会影响新的业务的执行。

1.5 影响新的业务操作
mysql> select * from tango.tt1;

该操作也会hang住,查看对应的锁情况,也是处于pending状态。也就是阻塞的DDL操作会影响接下去的业务对该表的访问,直到DDL超时失败后,后续的业务才会正常。

mysql> select * from performance_schema.metadata_locks where object_name='tt1';
+-------------+---------------+-------------+-------------+-----------------------+-------------------+---------------+-------------+-------------------+-----------------+----------------+
| OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | COLUMN_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE         | LOCK_DURATION | LOCK_STATUS | SOURCE            | OWNER_THREAD_ID | OWNER_EVENT_ID |
+-------------+---------------+-------------+-------------+-----------------------+-------------------+---------------+-------------+-------------------+-----------------+----------------+
| TABLE       | tango         | tt1         | NULL        |       140712801139968 | SHARED_READ       | TRANSACTION   | GRANTED     | sql_parse.cc:5768 |             123 |             21 |
| TABLE       | tango         | tt1         | NULL        |       140712793308528 | SHARED_WRITE      | TRANSACTION   | GRANTED     | sql_parse.cc:5768 |             123 |             22 |
| TABLE       | tango         | tt1         | NULL        |       140712926580592 | SHARED_UPGRADABLE | TRANSACTION   | GRANTED     | sql_parse.cc:5768 |             121 |             20 |
| TABLE       | tango         | tt1         | NULL        |       140712928177104 | EXCLUSIVE         | TRANSACTION   | PENDING     | mdl.cc:3753       |             121 |             20 |
| TABLE       | tango         | tt1         | NULL        |       140713468045808 | SHARED_READ       | TRANSACTION   | PENDING     | sql_parse.cc:5768 |             120 |              6 |
+-------------+---------------+-------------+-------------+-----------------------+-------------------+---------------+-------------+-------------------+-----------------+----------------+
5 rows in set (0.00 sec)
1.6 在这个场景下存在的问题
  • 事务处理报错时,业务层没有handle这个报错,并显示的去做commit或rollback;
  • 表分区的预创建和监控:对于分区表是要有预先创建分区的机制,每天或每月定时窗口创建一批分区,同时分区不足时能够及时告警出来;
  • 数据库层元数据锁等待超时:有些不重要的业务系统将lock_wait_timeout设置为600s设置更大,在该故障场景下是存在问题的,相当于DDL阻塞的这期间新的业务也会受到影响。所以将该参数设置到合理区间,比如5~60s是有必要的。

对于MySQL生态的数据库,事务内执行失败后数据库没有锁资源没有释放本身机制上没有问题,像国产数据库中TiDB、GoldenDB都有类似的现象。对于其它数据库,比如Oracle、PostgreSQL等,针对这个场景是什么样的表现,接下去以openGauss数据库为例进行验证。

2、openGauss数据库下故障场景模拟
2.1 登录openGauss单机版数据库,并创建分区表
gsql -d postgres -p 5432
[opgauss@tango-01 data]$ gsql -d postgres -p 5432
gsql ((openGauss-lite 5.0.2 build 48a25b11) compiled at 2024-05-14 10:41:04 commit 0 last mr  release)
openGauss=# create database tango;

tango=# CREATE TABLE tt1 (  
tango(#     id int NOT NULL, 
tango(#     sdate date NOT NULL,  
tango(#     c1 varchar(4) NOT NULL
tango(# )  
tango-# PARTITION BY RANGE(sdate) (  
tango(#     PARTITION p20240524 VALUES LESS THAN ('2024-05-25'),  
tango(#     PARTITION p20240525 VALUES LESS THAN ('2024-05-26') 
tango(# );
CREATE TABLE

tango=# \dt
Schema | Name | Type  |  Owner  |             Storage              
--------+------+-------+---------+----------------------------------
 public | tt1  | table | opgauss | {orientation=row,compression=no}
2.2 开启事务并插入数据
tango=# begin;
BEGIN
tango=# select * from tt1;
 id |        sdate        | c1  
----+---------------------+-----
  1 | 2024-05-25 00:00:00 | aaa
(1 row)

tango=# insert into tt1 values(3,'2024-05-28','ccc'); 
ERROR:  inserted partition key does not map to any table partition

提示报错分区不存在

2.3 另外开启一个任务执行新增分区操作
tango=# ALTER table tt1 ADD PARTITION p20240526 VALUES LESS THAN ('2024-05-27');
ALTER TABLE

可以看到分区是新增成功的。

2.4 查看这种场景下表的锁和事务状态信息
tango=# SELECT l.locktype,l.database,l.relation::regclass,l.page,l.tuple,l.virtualxid,l.transactionid,l.classid,l.objid,l.objsubid,l.pid,l.mode,l.granted FROM pg_locks l JOIN pg_class c ON l.relation = c.oid WHERE c.relname = 'tt1';
 locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid |       pid       |      mode       | granted 
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+-----------------+-----------------+---------
 relation |    16384 | tt1      |      |       |            |               |         |       |          | 140405684233984 | AccessShareLock | t
(1 row)

tango=# SELECT datname,pid,sessionid,usename,application_name,backend_start,xact_start,query_start,state,query FROM pg_stat_activity where datname='tango';
 datname |       pid       | sessionid | usename | application_name |         backend_start         |          xact_start           |          query_start          |      
  state        |                                                                        query                                                                        
---------+-----------------+-----------+---------+------------------+-------------------------------+-------------------------------+-------------------------------+------
---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------
 tango   | 140405684233984 |         8 | opgauss | gsql             | 2024-05-26 15:45:47.008274+08 | 2024-05-26 15:47:40.481015+08 | 2024-05-26 15:47:45.822262+08 | idle 
in transaction | select * from tt1;

当执行失败后,事务处于idle in transaction (aborted)状态,表锁持有的锁也不存在了。

tango=# SELECT l.locktype,l.database,l.relation::regclass,l.page,l.tuple,l.virtualxid,l.transactionid,l.classid,l.objid,l.objsubid,l.pid,l.mode,l.granted FROM pg_locks l JOIN pg_class c ON l.relation = c.oid WHERE c.relname = 'tt1';
 locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | pid | mode | granted 
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+-----+------+---------
(0 rows)

tango=# SELECT datname,pid,sessionid,usename,application_name,backend_start,xact_start,query_start,state,query FROM pg_stat_activity where datname='tango';
 datname |       pid       | sessionid | usename | application_name |         backend_start         |          xact_start           |          query_start          |      
       state             |                                                                        query                                                                    
    
---------+-----------------+-----------+---------+------------------+-------------------------------+-------------------------------+-------------------------------+------
-------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------
----
 tango   | 140405684233984 |         8 | opgauss | gsql             | 2024-05-26 15:45:47.008274+08 |                               | 2024-05-26 15:49:09.048895+08 | idle 
in transaction (aborted) | insert into tt1 values(3,'2024-05-28','ccc');

可以看到openGauss数据库和MySQL数据库在这种故障场景下的不同表现,对于openGauss数据库而言,当事务内处理失败后,事务已经被数据库rollback了,事务中所持有的表锁也相应的释放了,其它如Oracle、PostgreSQL数据库是有相同的表现。

其它数据库因为时间关系暂时不验证了,总结针对这个场景需要优化的点有:①业务开发时候对事务报错主动处理,并显示的执行commit或rollback操作;②数据库层设置合理的DDL超时时间;③对分区表进行预创建和有效的监控手段;④数据库的DDL操作和业务处理主流程松耦合,尽量在投产窗口执行。


参考资料:

  1. https://docs-opengauss.osinfra.cn/zh/docs/5.0.0-lite

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/648524.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++:单例模型、强制类型转换

目录 特殊类的设计不能被拷贝的类实现一个类,只能在堆上实例化的对象实现一个类,只能在栈上实例化的对象不能被继承的类 单例模式饿汉模式懒汉模式饿汉模式与懒汉模式的对比饿汉优缺点懒汉优缺点懒汉模式简化版本(C11) 单例释放问…

速看!打造专属数字化能力模型的七大关键!

在数字化浪潮中,企业如何打造适应自身发展的数字化能力模型?这是许多企业面临的重要课题。今天,通过众多企业使用蚓链数字化生态解决方案实践总结,为大家分享至关重要的七大经验,助你开启数字化转型之旅! 1…

栈和队列OJ题详解

一.有效的括号: 20. 有效的括号 - 力扣(LeetCode) 首先拿到这个题目,我的第一个思路是利用双指针来走,看看是不是匹配的 但是这种情况就把双指针的这个思路直接pass了,明明是匹配的括号,用双指…

protobuf学习

学习了下protobuf这个工具,可以用来序列化数据结构,而且效率很高,数据可以压缩的更小。 记录下,我这里主要在C#里使用,从NuGet程序包安装以下两个 安装好后可以在该程序目录找到 packages\Google.Protobuf.Tools.3.26.…

【计算机毕业设计】安卓054基于Android校园助手

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板&#xff…

离线强化学习基础知识之offline MBRL和MFRL

1 离线强化学习介绍 离线强化学习(也称为批量强化学习或完全脱策略强化学习)仅依赖于先前收集的数据集,无需进一步交互。它提供了一种利用先前收集的数据集的方法以自动学习决策策略。 离线强化学习可以被定义为 data-driven 形式的强化学习…

一篇文章讲透排序算法之堆排序

1.前言 在学习这篇文章之前,请大家先学习堆这一数据结构中堆的概念,向下调整算法,向下调整建堆。 有关堆的实现方式请参考:堆的实现 堆排序就是利用堆里面学习过的知识点进行排序,如何进行排序呢? 2.堆…

拓扑排序(概念 + 模板 + 例题)

概念 : 拓扑排序只有有向图有 &#xff0c; 可以判断图中是否有环 ; Kahn(卡恩)算法 过程 : 模板 : vector<int> a[N] , res ; int d[N] ; // 存放每个结点的入度 int n , x ;bool toposort() {queue<int> q;for(int i 1; i < n; i) if(d[i] 0) q.push…

python中GUI之tkinter 模块

目录 1.tkinter 模块使用 tkinter 介绍 创建一个简单的 GUI 给窗口添加小构件 小构件种类 小构件参数说明 查看某个小构件的所有方法和属性 常用小构件用法 Button&#xff1a;按钮用法 Label&#xff1a;标签用法 Radiobutton&#xff1a;单选按钮用法 Checkbutto…

月薪5万是怎样谈的?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测技术社区&#xff0c;星球号&#xff1a;63559049&#xff09;里的学员问&#xff1a;目前是晶圆厂的PE&#xff0c;但是想跳槽谈了几次薪水&#xff0c;都没法有大幅度的增长&#xff0c;该怎么办&#xff1f;“学得文武…

JavaWeb 请求响应路径调试

在使用mvc时&#xff0c;或许会遇到请求的页面响应不了&#xff0c;这种情况要对站下径。 站点根目录 启动服务器时&#xff0c;通常要知道哪个是站点根目录。相应在网页端的url的跟站点通常为http://localhost:8080/ &#xff0c;前端解析时用的是站点根目录。 <form act…

RT-Thread更改msh串口波特率

修改rt-thread文件下components下dirvers下serial.h文件里 #define RT_SERIAL_CONFIG_DEFAULT 里的默认波特率即可

这方法真牛B!论文降重从81%直降1.9%

目录 一、万字论文&#xff0c;从0到1&#xff0c;只需1小时二、获取途径三、论文从81&#xff05;降到1.9&#xff05;四、内容是别人的&#xff0c;话是自己的五、AI工具 --> 中文论文降重六、论文降重小技巧 一、万字论文&#xff0c;从0到1&#xff0c;只需1小时 通过O…

ROS2入门21讲__第20讲__RQT:模块化可视化工具

目录 前言 rqt介绍 日志显示 图像显示 发布话题数据/调用服务请求 绘制数据曲线 数据包管理 节点可视化 前言 ROS中的Rviz功能已经很强大了&#xff0c;不过有些场景下&#xff0c;我们可能更需要一些简单的模块化的可视化工具&#xff0c;比如只显示一个摄像头的图像…

INTERCONNECT模块中的 Circuit Layout Editor

INTERCONNECT模块中的 Circuit Layout Editor 正文 正文 打开 INTERCONNECT 模块后的工作界面如下&#xff1a; 我们可以通过 View->Windows 选取我们需要的工具窗口。 当然&#xff0c;用户也可以自己手动重新规划各个窗口的位置&#xff0c;但是此处&#xff0c;我们保…

反射获取方法的参数类型和参数名

如何获取方法的参数类型和参数名 示例&#xff0c;要获取的方法 获取参数类型和名称 Testpublic void testGetParamsName() throws Exception {LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer new LocalVariableTableParameterNameDiscoverer();Method[…

抖音IP地址频繁变动:背后的原因与解读

在抖音这个短视频平台的日常使用中&#xff0c;不少用户可能注意到了自己的IP地址有时会频繁变动。这种现象不仅引起了用户的好奇&#xff0c;也引发了关于个人隐私、账号安全以及平台政策的一系列讨论。那么&#xff0c;抖音IP地址换来换去什么意思&#xff1f;这背后又隐藏着…

langchain进阶一:特殊的chain,轻松实现对话,与数据库操作,抽取数据,以及基于本地知识库的问答

特殊的chain langchain中的Chain有很多,能够轻松实现部分需求,极致简化代码,但是实现效果与模型智慧程度有关 会话链 效果与LLMChain大致相同 javascript 复制代码 from langchain.chains import ConversationChain from langchain_community.llms import OpenAI conversat…

从零构建vue3+ts+vite项目打包及项目依赖配置

❗️❗️❗️❗️ 写在最前: 本文是根据B站作者 月光分层 视频vuets 工程化配置以及作者笔记稍作整理 &#x1f496;&#x1f496;作者B站地址https://space.bilibili.com/14110850 &#x1f496;&#x1f496;视频教程地址vuets 工程化配置 &#x1f496;&#x1f496;作者微信…

Nacos 2.x 系列【10】配置管理

文章目录 1. 概述2. 配置管理2.1 CRUD2.2 版本管理2.3 灰度管理2.4 监听管理2.5 推送轨迹2.6 示例代码2.6 聚合数据 1. 概述 在Nacos的架构图中&#xff0c;配置管理包含了配置CRUD、版本管理、灰度管理、监听管理、推送轨迹、聚合数据等功能。 在上篇文档中&#xff0c;我们…