GreatSQL社区

搜索

杨贵森

mysql中query_id和STATEMENT_ID在不同OS上的关系

杨贵森 已有 38 次阅读2025-3-26 20:54 |系统分类:原理&产品解读

问题描述

之前有位大佬发现show engine innodb status中的query_idperformance_schema.events_statements_current中的STATEMENT_ID对不上, 但是以前是能对上的. 参考效果图为:

本图的例子: statement_id=1199942697242 query_id=1646821658

那位大佬的例子是 statement_id=11762288106 query_id=3172353514 ,我们按照这个来讲

分析过程

这里简单记录一下分析过程.

官方文档

这种问题, 我们首先查看官方文档,曰:

STATEMENT_ID
The query ID maintained by the server at the SQL level. The value is unique for the server instance because these IDs are generated using a global counter that is incremented atomically. This column was added in MySQL 8.0.14.

即statement_id和query id是一样的东西. 看来我们问题结了!(第一层)

那为啥show engine innodb status查询出来的query id和events_statements里面记录的statement_id不一致呢???

源码:变量定义

文档可能描述不准确, 但我们可以查看源码来辅助验证.

query_id

query_id定义如下 (sql/sql_class.h)

  /*
    Id of current query. Statement can be reused to execute several queries
    query_id is global in context of the whole MySQL server.
    ID is automatically generated from mutex-protected counter.
    It's used in handler code for various purposes: to check which columns
    from table are necessary for this select, to check if it's necessary to
    update auto-updatable fields (like auto_increment and timestamp).
  */
  query_id_t query_id;

query_id类型是query_id_t, 全局自增的. 其结构如下(sql/mysqld.h):

/* query_id */
typedef int64 query_id_t;
extern std::atomic atomic_global_query_id;

即query_id是int64字节的.还有原子性!

statement_id

我们再来看看statement_id的定义(storage/perfschema/pfs_events_statements.h):

  /** STATEMENT_ID, from the SQL layer QUERY_ID. */
  ulonglong m_statement_id;

注释里面写了STATEMENT_ID就是QUERY_ID, 虽然一个是 int64一个是ulonglong

啊, 明明写的m_statement_id啊, 没事儿, 还有如下代码呢(storage/perfschema/pfs.cc):

pfs->m_statement_id = query_id

定义层分析

虽然它俩都是8字节的int, 但是涉及到符号问题啊. 我们再扒一扒源码:

//sql_parse.cc
thd->set_query_id(next_query_id());

//mysqld.h
/* increment query_id and return it.  */
[[nodiscard]] inline query_id_t next_query_id() {
  return ++atomic_global_query_id;
}

只是递增的话, 影响不大, 毕竟小端存储, 除非query量达到了9223372036854775808, 所以就目前来看query_id和statement_id是一样的. 印证了官方文档的说法, 看来我们问题确实结了!(第二层)

不对啊, 文章开头看到的query_id和statement_id为啥不一样,还是没有解释啊. 难道说在输出方面懂了手脚?

源码:信息输出

虽然定义上看, 两者是接近的, 均为8字节int. 但输出的时候,可能存在类型转换的可能. 比如8字节转为4字节.

于是我们再次翻阅源码,发现如下内容(sql/sql_thd_api.cc):

  len = snprintf(header, sizeof(header),
                 "MySQL thread id %u, OS thread handle %lu, query id %lu",
                 thd->thread_id(), (ulong)thd->real_id, (ulong)thd->query_id);

这不就是show engine innodb status信息么, 其中query id部分为(ulong)thd->query_id 即只输出无符合长整型的query id. 看起来貌似符合是这样的, 我们简单验证下呢.

比如, 文章开头的statement_id=11762288106, 我们使用sql转为4字节int看下呢

(root@127.0.0.1) [db1]> select 11762288106 & 0xffffffff;
+--------------------------+
| 11762288106 & 0xffffffff |
+--------------------------+
|               3172353514 |
+--------------------------+
1 row in set (0.00 sec)

只取4字节的值是3172353514, 和query id是一样的. 说明sql跑多了,输出时被截断了, 和官方描述,测试案例都吻合. 那这个案子就结了.(第三层)

截断的原因是兼容32bit OS?

复现

不是结了么, 咋还没完呢. 本着严谨的态度, 我们来复现一波. 即我们手动修改query_id的值, 让他超过ulong大小, 看是否和前面的结论一致. 修改这个值的方法很多, 可以使用python直接修改内存, 也可以直接gdb修改, 还可以跑2**32条sql, 我们选择使用gdb来操作.

通过上面的代码我们知道, query_id的值是来自atomic_global_query_id自增的, 所以我们只需要修改atomic_global_query_id的值即可.
# gdb接管mysqld进程
gdb -p `pidof mysqld`

# 查看当前的atomic_global_query_id的值
print atomic_global_query_id

# 修改atomic_global_query_id的值为11762288106
set atomic_global_query_id = 11762288106

# 退出并验证
quit


结果很奇怪, 设置atomic_global_query_id的值的时候就直接溢出了, 11762288106的16进制应该是0x2bd1645ea的, 但实际上是0xbd1645ea, 给我的2丢了. 岂有此理. 我们使用python来看下呢

确实没得高位的2了… 也就是说gdb那里就给我截断了. 岂有磁力, 还是使用python来修改吧…

import struct
f = open('/proc/22761/mem','r+b')
f.seek(0x398ff50,0)
data = struct.pack('<q',11762288106)
f.write(b'\xeaE\x16\xbd\x02\x00\x00\x00')


看起来是修改成功的. 我们使用gdb验证下呢.

看来确实是修改成功了的.

最后我们去数据库里面验证下.

发现query id超过了4字节的ulong ??? WTF!

深入分析

上面我们在linux环境上没有复现出来, 难道是我们分析得有问题吗? 代码都是那么写的啊! 不,错的不是我们,是世界 是操作系统. 那我们编写如下代码验证下:

#include 
#include  
#include <inttypes.h>

int main() {
	int64_t aa = 11762288106LL;
	printf("%%lu output -> %lu\n", aa);
	printf("%%%%lu output -> %llu\n", aa);
	return 0;
}

先看linux环境的


linux环境没有做转换. 和我们在linux上测试的结果一致.

再看看windows环境的


纳尼, 确实被截断了, 符合文章开头的例子.

那我们使用gdb来试试.

很遗憾, 不得行… 于是我们使用Windbg来调试:https://apps.microsoft.com/detail/9pgjgd53tn86?hl=zh-CN&gl=CN

然后我们使用如下调试命令来修改

?? mysqld!atomic_global_query_id._Storage._Value
x mysqld!atomic_global_query_id
输出为:00007ff7`15681540 mysqld!atomic_global_query_id = 51
dq 00007ff7`15681540 L1
eq 00007ff7`15681540 11762288106
dq 00007ff7`15681540 L1
?? mysqld!atomic_global_query_id._Storage._Value

看起来是修改成功了, 我们来验证下呢

啊, 这… 数据库咋挂了呢…
原来还得做下分离… .detach

然后我们再次登录数据库验证下:

哈哈,对上了.

其它

在上述例子里面, 我们使用status查看QPS, 会发现Questions和query id值是一样的, 于是我们的QPS就达到了8992575, 强者,恐怖如斯也!

总结

query_id是int64类型, statement_id是ulonglong类型, 而statement_id值来源于query_id.
所以

  1. 对于linux环境下 query_id = statement_id
  2. 但对于windows环境, query_id&0xffffffff = statement_id&0xffffffff 即只比较低位4字节内容
我们这里只考虑了64bit环境, 未考虑32bit环境(找不到…)

参考
https://dev.mysql.com/doc/refman/8.0/en/performance-schema-events-statements-current-table.html
https://github.com/mysql/mysql-server
https://apps.microsoft.com/detail/9pgjgd53tn86?hl=zh-CN&gl=CN


评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 立即注册

合作电话:010-64087828

社区邮箱:greatsql@greatdb.com

社区公众号
社区小助手
QQ群
GMT+8, 2025-3-31 06:35 , Processed in 0.015542 second(s), 9 queries , Redis On.
返回顶部