All Products
Search
Document Center

PolarDB:Statement Outline

Last Updated:Mar 28, 2026

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. Run CALL dbms_outln.preview_outline('<schema>', '<query>') — a non-empty result confirms the outline matched. Alternatively, run EXPLAIN on the target query and check whether the Extra column shows Using 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_name is set, both the database name and the SQL digest must match the outline rule.

  • If Schema_name is 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 outlineIndex hint outline
MechanismInjects /*+ ... */ hints inside the queryAppends USE INDEX, FORCE INDEX, or IGNORE INDEX to a table reference
ControlsJoin order, join algorithm, query block behavior, session variablesWhich index the optimizer uses for a specific table
MySQL 5.6 supportNoYes
Procedureadd_optimizer_outlineadd_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>');
ParameterDescription
Schema_nameThe database name. Leave blank to match by digest only.
HintThe complete optimizer hint string, such as /*+ MAX_EXECUTION_TIME(1000) */.
QueryThe original SQL statement to pin.
When Query contains double-quoted strings, enclose the entire Query in double quotes and use single quotes inside, or vice versa. An outline created with single quotes in Query matches 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>');
ParameterDescription
Schema_nameThe database name.
DigestA 64-byte hash string obtained by hashing Digest_text. Leave blank to let the system calculate it from Query. See STATEMENT_DIGEST().
PositionThe 1-based position of the table the hint applies to. For the Nth table in the SQL text, set Position to N.
TypeThe index hint type: USE INDEX, FORCE INDEX, or IGNORE INDEX.
HintA comma-separated list of index names, such as ind_1,ind_2.
ScopeRestricts the hint to a query type: FOR JOIN, FOR ORDER BY, or FOR GROUP BY. Leave blank to apply to all query types.
QueryThe 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 |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+------------------------------+
The Using outline <ID> text in Extra appears 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 OutlineSharding Outline
Table name matchingExact match, e.g., t_1Wildcard pattern, e.g., t_?
ScopeOne specific tableAll sharded tables with a consistent naming pattern
Rules requiredOne per sharded tableOne rule for all matching tables
MaintenanceHigh: batch create, update, and deleteLow: 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

  1. In the PolarDB console, go to Parameter Settings and set loose_outline_templated_digest_for_sharding_table to ON. For details, see Set parameters.

  2. Call add_optimizer_outline_sharding with any sharded table name as the Query. The procedure automatically templates trailing numbers into the t_? 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.

ParameterScopeDescription
loose_opt_outline_enabledGlobalEnables or disables Statement Outline. ON (default) or OFF.
loose_outline_templated_digest_for_sharding_tableSessionEnables 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'
FieldDescription
IdUnique outline ID.
Schema_nameDatabase name. Empty means match by digest only.
Digest64-byte hash string obtained by hashing Digest_text. See STATEMENT_DIGEST().
Digest_textNormalized form of the SQL statement used for digest calculation.
TypeOPTIMIZER for optimizer hints. USE INDEX, FORCE INDEX, or IGNORE INDEX for index hints.
ScopeIndex hint scope: FOR JOIN, FOR ORDER BY, or FOR GROUP BY. Empty string applies to all query types. Only used for index hints.
StateWhether the rule is active. Y (default) or N.
PositionFor 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.
HintFor 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.