在 OceanBase 数据库中,SQL_ID 用于唯一确定一个查询语句,它是由查询语句经过快速参数化后得到的字符串的 MD5 值,返回 128 位的哈希值。SQL_ID 的长度为 CHAR(32),形如 ED570339F2C856BA96008A29EDF04C74。相同文本的 SQL 的 SQL_ID 总是相同的。 目前 SQL_ID 的生成机制存在一个问题:SQL 语句中多一个空格、少一个空格、制表符或大小写不一致,等等都会导致生成的 SQL_ID 不一致,因此从 OceanBase 数据库 V4.2.3 版本开始,引入了 FORMAT_SQL_ID 的概念,在生成 FORMAT_SQL_ID 之前,优化器会先做一次忽略大小写、空格等非语法定义符号的操作,以及将 inlist 列表统一归一化为 in (?),这使得归一化后得到同样 Format SQL Text 或 Format SQL ID。通过 FORMAT_SQL_ID 绑定的 FORMAT OUTLINE 可以提供了一种更为宽松的匹配规则,满足业务 SQL 进行模糊绑定 Hint 的需求。
详细说明
如何生成 SQL_ID
直接使用
md5sum命令来生成SQL_ID。[root@localhost ~]# echo -n 'select count(*) as cnt from t1' | md5sum | tr 'a-z' 'A-Z' 69A55C8B8DF12C56A5701E058B96B602 - [root@localhost ~]# echo -n 'select count(*) as cnt from t1;' | md5sum | tr 'a-z' 'A-Z' 199FE00E13533C4F6F46DA949051D8E3 - [root@localhost ~]# echo -n 'select count(*) as cnt from T1;' | md5sum | tr 'a-z' 'A-Z' A3515081E488D7C246D9F3CCB2F9C1EF -使用数据库
md5函数来生成SQL_ID。MySQL [test]> select upper(md5("select count(*) as cnt from t1")); +----------------------------------------------+ | upper(md5("select count(*) as cnt from t1")) | +----------------------------------------------+ | 69A55C8B8DF12C56A5701E058B96B602 | +----------------------------------------------+ 1 row in set (0.001 sec) MySQL [test]> select upper(md5("select count(*) as cnt from t1;")); +-----------------------------------------------+ | upper(md5("select count(*) as cnt from t1;")) | +-----------------------------------------------+ | 199FE00E13533C4F6F46DA949051D8E3 | +-----------------------------------------------+ 1 row in set (0.001 sec) MySQL [test]> select upper(md5("select count(*) as cnt from T1;")); +-----------------------------------------------+ | upper(md5("select count(*) as cnt from T1;")) | +-----------------------------------------------+ | A3515081E488D7C246D9F3CCB2F9C1EF | +-----------------------------------------------+ 1 row in set (0.001 sec)使用 Python 脚本生成对应 SQL 的
SQL_ID。import hashlib sql_text='SELECT * FROM t1 WHERE c2 = ?' sql_id=hashlib.md5(sql_text.encode('utf-8')).hexdigest().upper() print(sql_id)
如何生成 FORMAT_SQL_ID
使用数据库
statement_digest_text和md5函数来生成SQL_ID。MySQL [(none)]> SELECT UPPER ( MD5(statement_digest_text('SELECT * from t1 where c1 in(1)'))) from dual; +------------------------------------------------------------------------+ | UPPER ( MD5(statement_digest_text('SELECT * from t1 where c1 in(1)'))) | +------------------------------------------------------------------------+ | DA73FD97F82D313ABDE0E1B547D900FE | +------------------------------------------------------------------------+ 1 row in set (0.007 sec) MySQL [(none)]> SELECT UPPER ( MD5(statement_digest_text('SELECT * FROM T1 WHERE C1 IN (1,2,3)'))) from dual; +-----------------------------------------------------------------------------+ | UPPER ( MD5(statement_digest_text('SELECT * FROM T1 WHERE C1 IN (1,2,3)'))) | +-----------------------------------------------------------------------------+ | DA73FD97F82D313ABDE0E1B547D900FE | +-----------------------------------------------------------------------------+ 1 row in set (0.002 sec) MySQL [(none)]> SELECT UPPER ( MD5(statement_digest_text('SELECT * from t1 where c1 in( 1,2,3,4,5)'))) from dual; +--------------------------------------------------------------------------------------------------+ | UPPER ( MD5(statement_digest_text('SELECT * from t1 where c1 in( 1,2,3,4,5)'))) | +--------------------------------------------------------------------------------------------------+ | DA73FD97F82D313ABDE0E1B547D900FE | +--------------------------------------------------------------------------------------------------+ 1 row in set (0.001 sec)
使用 SQL_ID 来创建 OUTLINE
语法:
CREATE [OR REPLACE] OUTLINE <outline_name> ON <sql_id> USING HINT <hint>;示例:
CREATE OUTLINE outline1 ON 'ED570339F2C856BA96008A29EDF04C74' USING HINT /*+ index(tbl1 idx_col2)*/; CREATE OUTLINE outline_GL_VOUCHER_JOIN ON '79B1A019A1384DFEF7468AADD43AA000' USING HINT /*+LEADING(C,B) USE_HASH(B,A) PARALLEL(16)*/; CREATE OUTLINE schema1_outline_prepare ON 'D9A0B5644CDF76E2ADF389FB8708545D' USING HINT /*+ USE_HASH(@"SEL$0208448F" "C"@"SEL$1") */;
使用 FORMAT_SQL_ID 来创建 FORMAT OUTLINE
语法:
CREATE [OR REPLACE] FORMAT OUTLINE <format_outline_name> ON <format_sql_id> USING HINT <hint>;示例:
CREATE FORMAT OUTLINE my_sql_id_outline ON "ED570339F2C856BA96008A29EDF04C74" USING HINT /*+ index(t1 idx_c2)*/; CREATE FORMAT OUTLINE fmt_otl_pub_org_group_detail ON "BEA5C4B7ECD64ECEB2D301FB5C49B6C7" USING HINT /*+ index(T0 uk_groupid_orgid_tenant_ytenant) */;
备注说明
在
GV$OB_SQL_AUDIT视图中可以查到一条 SQL 语句对应的SQL_ID和FORMAT_SQL_ID。允许普通 OUTLINE 和 FORMAT OUTLINE 同名,但通常强烈不建议二者同名。
当
SQL_ID相同时,使用SQL_TEXT方式创建的 OUTLINE/FORMAT OUTLINE 会覆盖使用SQL_ID方式创建的 OUTLINE/FORMAT OUTLINE,因为SQL_TEXT方式创建的优先级更高。当与某一条 SQL 语句关联的 NORMAL OUTLINE 与 FORMAT OUTLINE 同时存在时,优先匹配 NORMAL OUTLINE。
如果一条 SQL 语句涉及到跨库的查询,那么创建 OUTLINE/FORMAT OUTLINE 时需要先切换到执行这条 SQL 语句使用的 SCHEMA,否则 OUTLINE/FORMAT OUTLINE 是不会生效的。
如果希望让某条 SQL 语句执行时使用 OUTLINE/FORMAT OUTLINE 时,也一定要先切换到创建这个 OUTLINE/FORMAT OUTLIN E时使用的 SCHEMA 下面去执行该 SQL 语句,否则 SCHEMA/FORMAT OUTLINE 是不会生效的。
优化器计算 SQL_ID 时,首先会自动去掉普通的注释,并且会将常量都替换为
?,然后再计算SQL_ID。SQL 语句中间多一个空格、少一个空格、多一个换行符、少一个换行符,计算出来的
SQL_ID就会不一样的;SQL 语句末尾带分号(;)或者不带分号(;),计算出来的SQL_ID也是不一样的。手工执行的 SQL 语句,在
GV$OB_SQL_AUDIT中的QUERY_SQL会自动去掉末尾的分号、空格和换行符(SQL 语句中间的空格和换行符不会去掉),并且计算出来的SQL_ID、FORMAT_SQL应该默认是不带分号计算的。通过 Java 代码去执行的 SQL 语句,如果该 SQL 语句末尾带了多个空格的话,在
GV$OB_SQL_AUDIT中的QUERY_SQL不会自动去掉末尾的空格,但计算出来的SQL_ID/FORMAT_SQL会自动去掉末尾的空格。通过 Java 代码去执行的 SQL 语句,如果该 SQL 语句末尾带了分号(
;)的话,在GV$OB_SQL_AUDIT中的QUERY_SQL不会自动去掉末尾的分号,计算出来的SQL_ID/FORMAT_SQL也会自动包含末尾的分号。测试如下:
通过 Java 代码依次执行如下的 SQL 语句:
stmt.executeQuery("select * from test_table"); stmt.executeQuery("select * from test_table "); stmt.executeQuery("select * from test_table ;"); stmt.executeQuery("select * from test_table ; "); stmt.executeQuery("select * from test_table;"); stmt.executeQuery("/*this is a comment*/select * from test_table;"); stmt.executeQuery("/*this is a comment*/select * from test_table");查看
GV$OB_SQL_AUDIT中对应的执行记录如下:MySQL [test]> select usec_to_time(request_time),query_sql,sql_id,format_sql_id from oceanbase.gv$ob_sql_audit where query_sql like '%select * from test_table%' and query_sql not like '%usec_to_time%'; +----------------------------+------------------------------------------------+----------------------------------+----------------------------------+ | usec_to_time(request_time) | query_sql | sql_id | format_sql_id | +----------------------------+------------------------------------------------+----------------------------------+----------------------------------+ | 2025-11-19 10:33:13.728125 | select * from test_table | 57E61883B587911EEDA5D92864B678C4 | FABCF92646FA039AC30C90C13424739E | | 2025-11-19 10:33:48.371280 | select * from test_table | 57E61883B587911EEDA5D92864B678C4 | FABCF92646FA039AC30C90C13424739E | | 2025-11-19 10:34:20.094557 | select * from test_table ; | AB1BA3D09C2B4CE044CD3B8E36827F63 | AA3C2D99C7002C54289468458C4D6685 | | 2025-11-19 10:34:53.978846 | select * from test_table ; | AB1BA3D09C2B4CE044CD3B8E36827F63 | AA3C2D99C7002C54289468458C4D6685 | | 2025-11-19 10:35:56.549397 | select * from test_table; | 7D5A383130370F5F8AB5D4AF1525B089 | AA3C2D99C7002C54289468458C4D6685 | | 2025-11-19 10:40:36.187218 | /*this is a comment*/select * from test_table; | 7D5A383130370F5F8AB5D4AF1525B089 | AA3C2D99C7002C54289468458C4D6685 | | 2025-11-19 10:42:18.570059 | /*this is a comment*/select * from test_table | 57E61883B587911EEDA5D92864B678C4 | FABCF92646FA039AC30C90C13424739E | +----------------------------+------------------------------------------------+----------------------------------+----------------------------------+ 7 rows in set (0.038 sec)SQL 语句中的普通注释如
/* ApplicationName=xxx */不会影响 SQL 绑定 OUTLINE/FORMAT OUTLINE 的,因为优化器计算SQL_ID/FORMAT_SQL_ID时会首先自动去掉普通的注释。如果修改了 SQL 语句中表的别名后,对应的
SQL_ID/FORMAT_SQL_ID也会不一样的。
适用版本
OceanBase 数据库 V4.2.1 及更高版本。