MySQL:不是MySQL问题的MySQL问题

一、自定义函数的BUG导致的问题

这个问题是跑一条如下的的SQL

update test set p_id=getPid(c_id);

这个表只有10w条数据,但是语句却一直不能完成,如果将语句加上limit,当limit 50000的时候是可以执行完成的,但是当limit 80000的时候就一直不能完成。并且有一个现象,就是语句会不断会出现opening tables的状态。

 既然语句不能执行完成,那么就需要找到为什么不能完成,先把等待的原因找到,比如:

  • 锁等待?
  • CPU打满?
  • IO打满?

排查下来发现这个语句在实际执行的时候占用了大量的CPU,因此我们分别采集了正常执行和异常的情况,发现异常的时候正常的逻辑几乎成了一根线,而非正常的逻辑占用了大量的CPU如下:

那么很显然,实际上本语句执行异常的情况下,CPU都没有处理正常的逻辑。而其上层调用sp_head::execute_function就是执行函数的上层调用,而这里只有一个自定义函数,因此几乎可以判定是自定义函数内部逻辑遇到了什么问题。接着我们使用pstack对异常情况的执行栈进行了查看,并且多次测试正常逻辑的pstack执行栈,发现其中有一个逻辑入参不断在膨胀,且内存长度不断增加(length),

当然这里所有的都是我的测试环境的构建,不是线上环境。那么就可以确认函数内部在做拼接的时候遇到了问题,继而我们打开自定义函数getPid,发现其中有一个while循环,循环内部在做字段的拼接,拼接完成后返回值,就是这个while循环,在满足一定情况下会出现死循环,而且根据pstack入参这个字符串,实际上就是不断在拼接某个字段,这个字段的值为1,由于死循环拼接了很长很长,这里看到就是1,1,1,1,1,1……,这样我们也拿到了这个出现问题行的字段值 1,并且我们通过死循环条件也能判断出另外一个字段的值,接下来就根据这两个字段在表里面查一下就可以找到导致死循环的行,当然这里只是讲一个思路,不方便给出这个自定义函数。出现死循环的问题也刚好符合CPU打满的情况。

其次由于自定义函数内存有select 语句,这个语句在遇到自定义函数死循环的情况下要不断的循环跑,因此就观察到update 语句执行异常期间,观察到opening tables的情况。

二、应用代码static 变量导致的死锁

这个问题在MySQL层的表现就是出现了死锁,但是这个死锁表很简单,简单到只有少量的记录,而且只有主键,并且没有其他的索引这里假定主键就是id,且为RC隔离级别,每次执行的语句也是根据主键来查询和更新的,如下:

begin;
select * from test where id=1 for update;
update test set name='a' where id=1;
commit;

死锁如下(这里删除了详细数据):
------------------------
LATEST DETECTED DEADLOCK
------------------------
2022-07-06 19:48:38 0x7efc44162700
*** (1) TRANSACTION:
TRANSACTION 12739556, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 627119, OS thread handle 139619931977472, query id 129095157 192.168.1.81 root updating
update test set name='a' where id=1

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 388279 page no 4 n bits 152 index PRIMARY of table `test`.`test` trx id 12739556 lock_mode X locks rec but not gap
Record lock, heap no 82 PHYSICAL RECORD: n_fields 16; compact format; info bits 0

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 388279 page no 4 n bits 152 index PRIMARY of table `test`.`test` trx id 12739556 lock_mode X locks rec but not gap waiting
Record lock, heap no 55 PHYSICAL RECORD: n_fields 16; compact format; info bits 0

*** (2) TRANSACTION:
TRANSACTION 12739557, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 627114, OS thread handle 139621354526464, query id 129095158 192.168.1.81 root updating
update test set name='o' where id=2

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 388279 page no 4 n bits 152 index PRIMARY of table `test`.`test` trx id 12739557 lock_mode X locks rec but not gap
Record lock, heap no 55 PHYSICAL RECORD: n_fields 16; compact format; info bits 0

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 388279 page no 4 n bits 152 index PRIMARY of table `test`.`test` trx id 12739557 lock_mode X locks rec but not gap waiting
Record lock, heap no 82 PHYSICAL RECORD: n_fields 16; compact format; info bits 0

*** WE ROLL BACK TRANSACTION (2)

那么出现这种死锁问题,一般分析路径为:

  • 业务代码是否有问题。
  • 执行计划是否有问题。
  • 最后才是重现,分析MySQL本身的问题。

当我们分析第一点的时候,业务代码写得很简单,也很清晰就是前面的事务逻辑,这种事务说实话出现死锁貌似不太可能,因为很简单查询是查询的主键,更新的时候也是通过主键更新一个字段的值而已,且除了主键没有其他的索引,这种情况一般只会是堵塞而不会出现死锁。

然后我们在测试环境模拟死锁的时候打开了general log,发现并不是我们想象的,多线程的各个语句和事务是在一个session 交替进行的,这就奇怪了,言外之意就是多个业务线程对应了一个session,大概如下:

begin;
update set name='o' where id=2
commit;
begin;
select * from test where id=1 for update;
select * from test where id=3 for update;
select * from test where id=4 for update;
update test set name='a' where id=3;
update test set name='a' where id=1;
commit;
update set name='o' where id=4;

反正没什么规律,这貌似很像多线程并发并且所有语句堆到了同一个session。

那么进而分析,代码变量的定义我们才发现代码中将连接变量的属性设置为了static类型的,开发环境当然是java的 ,我们可以类比C++,C++中如果将类变量的属性加上static代表是静态变量,这种变量的值不是存在栈上的,而是存在静态全局区,所有通过本类实例化的对象,都共享了这个静态变量,换一句话说,如果某个实例化的对象修改了这个静态变量那么所有的实例化对象都会修改,当然java/python 都有类似的使用方法。主要还是看内存到底是栈内存/堆内存/全局内存。那么这个问题就变得简单了,当多个线程同时初始化建立好连接过后,所有的线程实际上最后得到连接只有一个。类似如下:

最后为了验证我写了一个测试用例(见末尾),很难跑成功,因为4个线程同时使用了一个connect,感觉应该是C下面这样在获取结果(mysql_store_result)和free结果(mysql_free_result)的时候可能的情况是未知的,当然也没去仔细研究lib库函数的使用方式可能写的方式也有问题,反正各种crash(core dump)。但是在偶尔能够成功的时候可以在general log中看到如下日志,这里就是所有线程的语句堆到同一个session:

static变量:
2022-07-08T07:07:50.364174Z 173 Query select 1
2022-07-08T07:07:50.365168Z 173 Query select 2
2022-07-08T07:07:50.365903Z 173 Query select 3
2022-07-08T07:07:50.370390Z 173 Query select 0
2022-07-08T07:07:51.367748Z 173 Query select 2
2022-07-08T07:07:51.367903Z 173 Query select 1
2022-07-08T07:07:51.368161Z 173 Query select 3

显然这是一个session id 为173,而实际上测试用例4个线程会不断的跑select 0/select 1/select 2/select 3。但是4个线程对应了同一个session,这也和我们实际情况一致,这样如果多个应用各自启动了多个线程,那么混跑语句就会出现下面的情况:

app1 多线程:                                                               
begin;
select * from test where id=1 for update;
select * from test where id=2 for update;
select * from test where id=3 for update;
update set name='a' where id=1;
update set name='a' where id=2;
commit;

app2 多线程:
begin;
select * from test where id=2 for update;
select * from test where id=1 for update;
select * from test where id=3 for update;
update set name='a' where id=2;
update set name='a' where id=3;
commit;

事务被无序的扩大了,死锁概率当然大大增加。这也是我们实际环境中看到的情况。当然如果测试用例使用局部变量就没有问题,改为局部变量后正常执行如下:

2022-07-08T07:18:22.582624Z       225 Query     select 0
2022-07-08T07:18:22.582732Z 222 Query select 2
2022-07-08T07:18:22.582638Z 223 Query select 1
2022-07-08T07:18:22.583214Z 224 Query select 3
2022-07-08T07:18:23.583894Z 225 Query select 0
2022-07-08T07:18:23.583973Z 222 Query select 2
2022-07-08T07:18:23.583915Z 223 Query select 1
2022-07-08T07:18:23.584315Z 224 Query select 3

这里就是4个thread对应了4个session,各自跑的各自的语句。

附件

C++ 测试用例,如果改成局部变量后4个线程对应4个session,可以正常跑没有问题如下,static 变量容易导致各种crash。

#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include "/opt/mysql/mysql3306/install/include/mysql.h"
#include <time.h>
#include <unistd.h>

using namespace std;


class My_Con
{
public:
MYSQL conn_ptr;
My_Con(const char *host,const char *user,const char *passwd,unsigned int port)
{
mysql_init(&conn_ptr);
if(mysql_real_connect(&conn_ptr,host,user,passwd,NULL,port,NULL,0)==NULL)
{
printf("err: mysql_real_connect() error %s\n",mysql_error(&conn_ptr));
exit(1);
}
}
MYSQL* get_conn()
{
return &conn_ptr;
}
//~My_Con(){mysql_close(&conn_ptr);cout<<"close connect"<<endl;}
};


class My_Test
{
public:
static MYSQL* conn_ptr; //静态指针
static My_Con* test; //静态指针
int myid;
MYSQL_RES *query_res;
char strtest[30];

My_Test(const int i)
{
test = new My_Con("192.168.1.61","testuser","gelc123",3306);
conn_ptr = test->get_conn();
myid = i ;
query_res = NULL;
}
void* get_string(int id)
{
sprintf(strtest, "select %d ;", id);
cout<<strtest<<endl;
}



void test_query()
{
get_string(myid);
if(mysql_query(conn_ptr,strtest) != 0)
{
printf("err: mysql_query() error %s %s\n",mysql_error(conn_ptr),strtest);
//exit(1);
}

query_res=mysql_store_result(conn_ptr);
if(query_res == NULL)
{
;
}
mysql_free_result(query_res);

}
//TIPS: static variables
// ~My_Test(){delete []test;}

};
My_Con* My_Test::test = NULL;
MYSQL* My_Test::conn_ptr = NULL;


void* test_func(void* arg)
{
My_Test a(*((int*)arg)); //建立连接
struct timespec n_sec;
n_sec.tv_sec = 1;
n_sec.tv_nsec = 0;

for(;;)
{
nanosleep(&n_sec,NULL);
a.test_query();
}

}


int main()
{
pthread_t tid[4];
int tid_num = 0;
int i = 0;
int ret = 0;
int seq[4] = {0,1,2,3};

pthread_create(tid+tid_num,NULL,test_func,(void*)seq);
tid_num++;
pthread_create(tid+tid_num,NULL,test_func,(void*)(seq+1));
tid_num++;
pthread_create(tid+tid_num,NULL,test_func,(void*)(seq+2));
tid_num++;
pthread_create(tid+tid_num,NULL,test_func,(void*)(seq+3));
tid_num++;

//堵塞回收
for(i = 0;i<=tid_num;i++)
{
ret = pthread_join( *(tid+i) , NULL );
}
return 0 ;
}

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/229897.html<

(0)
运维的头像运维
上一篇2025-04-18 23:51
下一篇 2025-04-18 23:52

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注