When execution plans change unexpectedly — due to data growth, statistics drift, or version upgrades — you typically need to add optimizer hints directly to the SQL. If you cannot modify application code, Statement Outline lets you inject those hints without touching the SQL. It fixes the execution plan for a specific statement by matching a normalized SQL digest and rewriting the query with the configured hint before the optimizer sees it.
PolarDB provides the DBMS_OUTLN package with stored procedures to add, preview, inspect, and delete outlines.
After adding an outline, verify it works immediately. RunCALL dbms_outln.preview_outline('<schema>', '<query>')— a non-empty result confirms the outline matched. Alternatively, runEXPLAINon the target query and check whether theExtracolumn showsUsing outline <ID>.
Prerequisites
Before you begin, ensure that you have:
A PolarDB for MySQL cluster running one of the following versions:
PolarDB for MySQL 5.6 with minor version 5.6.1.0.36 or later
PolarDB for MySQL 5.7 with minor version 5.7.1.0.2 or later
PolarDB for MySQL 8.0.1 with minor version 8.0.1.1.1 or later
PolarDB for MySQL 8.0.2
To check your cluster version, see View the version number.
PolarDB for MySQL 5.6 does not support optimizer hints. Use add_index_outline (an index hint) for version 5.6 clusters.How it works
Statement Outline intercepts a matching SQL statement and rewrites it with the configured hint before the optimizer sees it. Matching is based on a normalized digest of the SQL text, optionally scoped to a specific database:
If
Schema_nameis set, both the database name and the SQL digest must match the outline rule.If
Schema_nameis blank, only the SQL digest must match.
All hints are persisted in the mysql.outline system table and loaded into memory at startup.
Choose an outline type
| Optimizer hint outline | Index hint outline | |
|---|---|---|
| Mechanism | Injects /*+ ... */ hints inside the query | Appends USE INDEX, FORCE INDEX, or IGNORE INDEX to a table reference |
| Controls | Join order, join algorithm, query block behavior, session variables | Which index the optimizer uses for a specific table |
| MySQL 5.6 support | No | Yes |
| Procedure | add_optimizer_outline | add_index_outline |
Quick start
The following examples show the most common outline operations. All examples use CALL dbms_outln.<procedure>() syntax.
Fix a query with an optimizer hint
Force a specific index using an optimizer hint:
CALL dbms_outln.add_optimizer_outline(
'test', -- Schema_name
'/*+ INDEX(t1 i_a) */', -- Hint string
'SELECT test.t1.a AS a FROM test.t1' -- Original SQL
);Verify the outline matched:
CALL dbms_outln.preview_outline('test', 'SELECT test.t1.a AS a FROM test.t1');Fix a query with an index hint
Use add_index_outline to apply a USE INDEX hint. This approach works on MySQL 5.6 clusters:
CALL dbms_outln.add_index_outline(
'outline_db', -- Schema_name
'', -- Digest (leave blank; the system calculates it from Query)
1, -- Position: the 1st table in the SQL
'USE INDEX', -- Type
'ind_1', -- Index name(s)
'', -- Scope (blank = all query types)
"SELECT * FROM t1 WHERE t1.col1 = 1 AND t1.col2 = 'xpchild'" -- Original SQL
);Fix join order
Force it1 and it2 to join first, leaving the optimizer to handle remaining tables:
CALL dbms_outln.add_optimizer_outline(
'outline_db',
'/*+ JOIN_PREFIX(it1, it2) */',
'SELECT it3.id3, it2.i2, it1.id2
FROM t3 it3, t1 it1, t2 it2
WHERE it3.i3 = it1.id1
AND it2.id2 = it1.id2
GROUP BY it3.id3, it1.id2'
);Set a session variable for one statement
Apply SET_VAR hints to override a session variable for a single statement execution:
CALL dbms_outln.add_optimizer_outline(
'test',
'/*+ SET_VAR(max_execution_time=1) */',
'SELECT * FROM t1'
);Route to row-store or columnstore read-only nodes
In clusters with In-Memory Columnar Index (IMCI) read-only nodes, force statement routing with SET_VAR:
-- Force execution on columnstore (IMCI) read-only nodes
CALL dbms_outln.add_optimizer_outline('test', '/*+ SET_VAR(cost_threshold_for_imci=0) */', 'SELECT test.t1.a AS a FROM test.t1');
-- Force execution on row-store read-only nodes
CALL dbms_outln.add_optimizer_outline('test', '/*+ SET_VAR(use_imci_engine=OFF) */', 'SELECT test.t1.a AS a FROM test.t1');Manage outlines
Add an optimizer hint outline
CALL dbms_outln.add_optimizer_outline('<Schema_name>', '<Hint>', '<Query>');| Parameter | Description |
|---|---|
Schema_name | The database name. Leave blank to match by digest only. |
Hint | The complete optimizer hint string, such as /*+ MAX_EXECUTION_TIME(1000) */. |
Query | The original SQL statement to pin. |
WhenQuerycontains double-quoted strings, enclose the entireQueryin double quotes and use single quotes inside, or vice versa. An outline created with single quotes inQuerymatches the SQL regardless of whether you run it with single or double quotes.
Example: Pin an execution time limit to a query:
Original SQL:
SELECT * FROM t1 WHERE name = "Tom";Normalize quotes for the outline:
SELECT * FROM t1 WHERE name = 'Tom';Add the outline:
CALL dbms_outln.add_optimizer_outline("", "/*+ max_execution_time(1000) */", "SELECT * FROM t1 WHERE name='Tom'");Add an index hint outline
CALL dbms_outln.add_index_outline('<Schema_name>', '<Digest>', <Position>, '<Type>', '<Hint>', '<Scope>', '<Query>');| Parameter | Description |
|---|---|
Schema_name | The database name. |
Digest | A 64-byte hash string obtained by hashing Digest_text. Leave blank to let the system calculate it from Query. See STATEMENT_DIGEST(). |
Position | The 1-based position of the table the hint applies to. For the Nth table in the SQL text, set Position to N. |
Type | The index hint type: USE INDEX, FORCE INDEX, or IGNORE INDEX. |
Hint | A comma-separated list of index names, such as ind_1,ind_2. |
Scope | Restricts the hint to a query type: FOR JOIN, FOR ORDER BY, or FOR GROUP BY. Leave blank to apply to all query types. |
Query | The original SQL statement to pin. |
Example:
CALL dbms_outln.add_index_outline(
'outline_db', '', 1, 'USE INDEX', 'ind_1', '',
"SELECT * FROM t1 WHERE t1.col1 = 1 AND t1.col2 = 'xpchild'"
);Preview an outline
preview_outline shows which outlines match a given query without executing the query. Use this to validate an outline before relying on it in production.
CALL dbms_outln.preview_outline('<Schema_name>', '<Query>');Example:
CALL dbms_outln.preview_outline('outline_db', "SELECT * FROM t1 WHERE t1.col1 = 1 AND t1.col2 = 'xpchild'");Output:
+------------+------------------------------------------------------------------+------------+------------+-------+---------------------+
| SCHEMA | DIGEST | BLOCK_TYPE | BLOCK_NAME | BLOCK | HINT |
+------------+------------------------------------------------------------------+------------+------------+-------+---------------------+
| outline_db | b4369611be7ab2d27c85897632576a04bc08f50b928a1d735b62d0a140628c4c | TABLE | t1 | 1 | USE INDEX (`ind_1`) |
+------------+------------------------------------------------------------------+------------+------------+-------+---------------------+
1 row in set (0.01 sec)View active outlines
show_outline lists all outlines currently loaded in memory, along with hit and overflow counts.
CALL dbms_outln.show_outline();Output:
+------+------------+------------------------------------------------------------------+-----------+-------+------+-------------------------------------------------------+------+----------+-------------------------------------------------------------------------------------+
| ID | SCHEMA | DIGEST | TYPE | SCOPE | POS | HINT | HIT | OVERFLOW | DIGEST_TEXT |
+------+------------+------------------------------------------------------------------+-----------+-------+------+-------------------------------------------------------+------+----------+-------------------------------------------------------------------------------------+
| 33 | outline_db | 36bebc61fce7e32b93926aec3fdd790dad5d895107e2d8d3848d1c60b74bcde6 | OPTIMIZER | | 1 | /*+ SET_VAR(foreign_key_checks=OFF) */ | 1 | 0 | SELECT * FROM `t1` WHERE `id` = ? |
| 32 | outline_db | 36bebc61fce7e32b93926aec3fdd790dad5d895107e2d8d3848d1c60b74bcde6 | OPTIMIZER | | 1 | /*+ MAX_EXECUTION_TIME(1000) */ | 2 | 0 | SELECT * FROM `t1` WHERE `id` = ? |
| 34 | outline_db | d4dcef634a4a664518e5fb8a21c6ce9b79fccb44b773e86431eb67840975b649 | OPTIMIZER | | 1 | /*+ BNL(t1,t2) */ | 1 | 0 | SELECT `t1` . `id` , `t2` . `id` FROM `t1` , `t2` |
| 35 | outline_db | 5a726a609b6fbfb76bb8f9d2a24af913a2b9d07f015f2ee1f6f2d12dfad72e6f | OPTIMIZER | | 2 | /*+ QB_NAME(subq1) */ | 2 | 0 | SELECT * FROM `t1` WHERE `t1` . `col1` IN ( SELECT `col1` FROM `t2` ) |
| 36 | outline_db | 5a726a609b6fbfb76bb8f9d2a24af913a2b9d07f015f2ee1f6f2d12dfad72e6f | OPTIMIZER | | 1 | /*+ SEMIJOIN(@subq1 MATERIALIZATION, DUPSWEEDOUT) */ | 2 | 0 | SELECT * FROM `t1` WHERE `t1` . `col1` IN ( SELECT `col1` FROM `t2` ) |
| 30 | outline_db | b4369611be7ab2d27c85897632576a04bc08f50b928a1d735b62d0a140628c4c | USE INDEX | | 1 | ind_1 | 3 | 0 | SELECT * FROM `t1` WHERE `t1` . `col1` = ? AND `t1` . `col2` = ? |
| 31 | outline_db | 33c71541754093f78a1f2108795cfb45f8b15ec5d6bff76884f4461fb7f33419 | USE INDEX | | 2 | ind_2 | 1 | 0 | SELECT * FROM `t1` , `t2` WHERE `t1` . `col1` = `t2` . `col1` AND `t2` . `col2` = ? |
+------+------------+------------------------------------------------------------------+-----------+-------+------+-------------------------------------------------------+------+----------+-------------------------------------------------------------------------------------+HIT: the number of times this outline was matched and applied.OVERFLOW: the number of times the outline could not find the target query block or table in the statement.
Delete an outline
CALL dbms_outln.del_outline(<Id>);Pass the ID from show_outline. The outline is removed from both memory and the mysql.outline system table immediately.
Example:
CALL dbms_outln.del_outline(32);If the ID does not exist, the system records a warning rather than an error:
CALL dbms_outln.del_outline(1000);
-- Query OK, 0 rows affected, 2 warnings (0.00 sec)
SHOW WARNINGS;
+---------+------+----------------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------------+
| Warning | 7521 | Statement outline 1000 is not found in table |
| Warning | 7521 | Statement outline 1000 is not found in cache |
+---------+------+----------------------------------------------+Verify an outline is applied
After adding an outline, confirm it is working with either of these methods.
Method 1: preview_outline (recommended)
CALL dbms_outln.preview_outline('outline_db', "SELECT * FROM t1 WHERE t1.col1 = 1 AND t1.col2 = 'xpchild'");A non-empty result set means the outline matched.
Method 2: EXPLAIN
Run EXPLAIN on the target query. If the outline was applied, the Extra column includes Using outline <ID>.
EXPLAIN SELECT * FROM t1 WHERE t1.col1 = 1 AND t1.col2 = 'xpchild';+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+------------------------------+
| 1 | SIMPLE | t1 | NULL | ref | ind_1 | ind_1 | 5 | const | 1 | 100.00 | Using where; Using outline 1 |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+------------------------------+TheUsing outline <ID>text inExtraappears only in:
PolarDB for MySQL 8.0.1 with minor version 8.0.1.0.34 or later
PolarDB for MySQL 8.0.2 with minor version 8.0.2.2.27 or later
To see the rewritten SQL, run SHOW WARNINGS after EXPLAIN:
SHOW WARNINGS;
+-------+------+----------------------------------------------------------+
| Level | Code | Message |
+-------+------+----------------------------------------------------------+
| Note | 1003 | /* select#1 */ SELECT `outline_db`.`t1`.`id` AS `id`,... USE INDEX (`ind_1`) WHERE ... |
+-------+------+----------------------------------------------------------+Sharding Outline
In table sharding scenarios, physical tables follow a numeric suffix pattern — t_001, t_002, ..., t_999. A standard Statement Outline matches only the exact table name, requiring a separate rule per table. In a large sharding setup this becomes unmanageable.
Sharding Outline solves this by treating trailing numbers as a wildcard pattern. It recognizes t_1, t_2, and t_100 as the single pattern t_?, so one rule covers all matching tables.
| Standard Outline | Sharding Outline | |
|---|---|---|
| Table name matching | Exact match, e.g., t_1 | Wildcard pattern, e.g., t_? |
| Scope | One specific table | All sharded tables with a consistent naming pattern |
| Rules required | One per sharded table | One rule for all matching tables |
| Maintenance | High: batch create, update, and delete | Low: single rule manages all matched tables |
Supported versions:
PolarDB for MySQL 8.0.1 with minor version 8.0.1.1.54 or later
PolarDB for MySQL 8.0.2 with minor version 8.0.2.2.33 or later
Enable Sharding Outline
In the PolarDB console, go to Parameter Settings and set
loose_outline_templated_digest_for_sharding_tabletoON. For details, see Set parameters.Call
add_optimizer_outline_shardingwith any sharded table name as theQuery. The procedure automatically templates trailing numbers into thet_?pattern.
CALL dbms_outln.add_optimizer_outline_sharding(
'test', -- Schema_name
'', -- Digest (leave blank)
1, -- Position
'/*+ MAX_EXECUTION_TIME(1000) */', -- Hint string
"SELECT t_1.c_1 FROM t_1" -- Any sharded table name works: t_1, t_2, t_100
);After execution, the rule applies to all tables matching t_? — including t_1, t_2, and t_100.
Parameters
Configure Statement Outline behavior in the PolarDB console under Parameter Settings. See Set parameters.
| Parameter | Scope | Description |
|---|---|---|
loose_opt_outline_enabled | Global | Enables or disables Statement Outline. ON (default) or OFF. |
loose_outline_templated_digest_for_sharding_table | Session | Enables or disables Sharding Outline. ON (default) or OFF. Requires PolarDB for MySQL 8.0.1 minor version 8.0.1.1.54 or later, or 8.0.2 minor version 8.0.2.2.33 or later. |
Appendix: Statement Outline system table
PolarDB stores all outline rules in mysql.outline. The table is created automatically at startup.
CREATE TABLE `mysql`.`outline` (
`Id` bigint(20) NOT NULL AUTO_INCREMENT,
`Schema_name` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`Digest` varchar(64) COLLATE utf8_bin NOT NULL,
`Digest_text` longtext COLLATE utf8_bin,
`Type` enum('IGNORE INDEX','USE INDEX','FORCE INDEX','OPTIMIZER')
CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`Scope` enum('','FOR JOIN','FOR ORDER BY','FOR GROUP BY')
CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '',
`State` enum('N','Y') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'Y',
`Position` bigint(20) NOT NULL,
`Hint` text COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Statement outline'| Field | Description |
|---|---|
Id | Unique outline ID. |
Schema_name | Database name. Empty means match by digest only. |
Digest | 64-byte hash string obtained by hashing Digest_text. See STATEMENT_DIGEST(). |
Digest_text | Normalized form of the SQL statement used for digest calculation. |
Type | OPTIMIZER for optimizer hints. USE INDEX, FORCE INDEX, or IGNORE INDEX for index hints. |
Scope | Index hint scope: FOR JOIN, FOR ORDER BY, or FOR GROUP BY. Empty string applies to all query types. Only used for index hints. |
State | Whether the rule is active. Y (default) or N. |
Position | For optimizer hints: the 1-based position of the query block the hint targets. For index hints: the 1-based position of the table in the SQL text. |
Hint | For optimizer hints: the full hint string, e.g., /*+ MAX_EXECUTION_TIME(1000) */. For index hints: a comma-separated list of index names, e.g., ind_1,ind_2. |