---
title: 使用 SQL_ID 和 FORMAT_SQL_ID 来绑定执行计划的说明-OceanBase数据库使用指南
description: 了解OceanBase数据库在实际应用中关于 使用 SQL_ID 和 FORMAT_SQL_ID 来绑定执行计划的说明相关的常见问题和使用技巧，帮助您快速解决 使用 SQL_ID 和 FORMAT_SQL_ID 来绑定执行计划的说明的难题。
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

划线反馈

# 使用 SQL_ID 和 FORMAT_SQL_ID 来绑定执行计划的说明

更新时间：2026-05-15 09:06

适用版本： V4.2.x、V4.3.x、V4.4.x 内容类型：TechNote  

在 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`。

  ```shell
  [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`。

  ```sql
  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`。

  ```python
  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`。

  ```sql
  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

- **语法：**

  ```sql
  CREATE [OR REPLACE] OUTLINE <outline_name> ON <sql_id> USING HINT <hint>;

  ```
 - **示例：**

  ```sql
  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

- **语法：**

  ```sql
  CREATE [OR REPLACE] FORMAT OUTLINE <format_outline_name> ON <format_sql_id> USING HINT <hint>;

  ```sql
  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 语句：**

  ```shell
  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` 中对应的执行记录如下：**

  ```sql
  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 及更高版本。

上一篇

[Outline / Hint 常见问题](https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000001166244)

下一篇

[Oracle 模式下开启 PS 和 cursor，SQL Audit 记录的 PARAMS_VALUE 是空的](https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000000232770) ![有帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*y6ocSqN8cqsAAAAAAAAAAAAAARQnAQ)![无帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*BG9IQJyLHF8AAAAAAAAAAAAAARQnAQ)![反馈](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*eTWdQKCRKHwAAAAAAAAAAAAAARQnAQ)[AI](https://www.oceanbase.com/obi) 咨询热线
