When you can't modify SQL in your application—because queries are auto-generated by middleware or a code release takes too long—use statement outline (OUTLINE) to inject optimizer hints at the database layer and stabilize execution plans without touching application code.
How it works
OUTLINE associates a hint with a class of SQL statements identified by a SQL_ID. When a query matches the SQL_ID, the database applies the bound hints before optimization, overriding any hints embedded in the original SQL.
All OUTLINE operations go through the hint_plan schema:
| Operation | Function or view |
|---|---|
| Create an OUTLINE | hint_plan.create_outline() |
| View OUTLINEs | hint_plan.outlines_status |
| Enable an OUTLINE | hint_plan.enable_outline() |
| Disable an OUTLINE | hint_plan.disable_outline() |
| Delete an OUTLINE | hint_plan.del_outline() |
Supported versions
| PostgreSQL version | Minimum minor engine version |
|---|---|
| PostgreSQL 16 | 2.0.16.9.9.0 |
| PostgreSQL 14 | 2.0.14.13.28.0 |
To check your minor engine version, run SHOW polardb_version; or view it in the console. If the version is not supported, upgrade the minor engine version.
Prerequisites
Before you begin, ensure that you have:
A PolarDB for PostgreSQL cluster running a supported version (see Supported versions)
The
pg_hint_planextension installed at version 1.4.1 or laterAn account with permissions to run DDL statements in the target database
Enable the OUTLINE feature
Step 1. Verify that pg_hint_plan is installed at version 1.4.1 or later:
SELECT extname, extversion >= '1.4.1' AS outline_version_ok
FROM pg_extension
WHERE extname = 'pg_hint_plan';Expected output:
extname | outline_version_ok
--------------+--------------------
pg_hint_plan | t
(1 row)If the output differs, resolve the issue before proceeding:
| Issue | Resolution |
|---|---|
| Extension not installed | Run CREATE EXTENSION IF NOT EXISTS pg_hint_plan; |
| Version too old | Run ALTER EXTENSION pg_hint_plan UPDATE; |
Run these statements in the target database using an account with the appropriate permissions.
Step 2. Set the pg_hint_plan.polar_enable_outline parameter to on. Go to in the console. This change does not restart the cluster.
Create an OUTLINE
Pass the SQL statement with the desired hint to hint_plan.create_outline(). The function extracts the hint and binds it to the SQL class.
CALL hint_plan.create_outline($$ SELECT /*+ Set(enable_bitmapscan off) */ * FROM t WHERE a = 1 $$);If you getERROR: invalid transaction terminationwhen running this statement from Data Management Service (DMS), switch to another client such aspsql. See Connect to a database cluster.
SQL matching rules
The SQL text you pass must match your application's query template. The following factors do not affect matching:
| Factor | Example |
|---|---|
| Whitespace, line breaks, and comments | Any extra spaces or -- comment |
| Parameter values (constants and variables) | a = 1, a = $1, and a = 2 are treated identically |
| Keyword case | SELECT, select, and Select are equivalent |
| Number of parameters in a list | a IN (1,2,3) and a IN (1,2) match the same SQL class. Exception: a IN (1) (single parameter) does NOT match a IN (1,2,3) (multiple parameters), because a single-element list is not normalized with a multi-element list. |
The following factors do prevent a match:
Extra type casts (
::), for examplea::intSchema-qualified table names, for example
public.tinstead oftDifferent case for table or column names
View OUTLINEs
Query hint_plan.outlines_status to see all OUTLINEs in the current database:
SELECT * FROM hint_plan.outlines_status;| Column | Type | Description |
|---|---|---|
id | BIGINT | Auto-generated primary key that uniquely identifies each OUTLINE |
sql_id | BIGINT | The SQL_ID that this OUTLINE is bound to |
hints | TEXT | The hints bound to this OUTLINE |
state | CHARACTER(1) | Y = enabled, N = disabled |
depends_rels | TEXT[] | All relations (tables and views) that the stabilized plan depends on |
query_string | TEXT | The SQL statement used when creating this OUTLINE |
create_user | TEXT | The user who created this OUTLINE |
create_time | TIMESTAMP WITHOUT TIME ZONE | When the OUTLINE was created |
total_hints | TEXT | Aggregated hints for the SQL_ID. If multiple OUTLINEs share the same SQL_ID, their hints are combined in ascending id order |
calls | BIGINT | How many times this OUTLINE has been applied |
Enable or disable an OUTLINE
Use the OUTLINE id from hint_plan.outlines_status:
-- Enable OUTLINE with id = 1
CALL hint_plan.enable_outline(1);
-- Disable OUTLINE with id = 1
CALL hint_plan.disable_outline(1);Delete an OUTLINE
CALL hint_plan.del_outline(1);Usage notes
OUTLINE takes priority over inline hints. After you create an OUTLINE for a SQL class, any hints embedded in the original SQL are ignored. Only the OUTLINE hints apply.
Multiple OUTLINEs stack. You can create more than one OUTLINE for the same SQL class. The hints are combined in ascending
idorder.OUTLINE and `hint_table` are mutually exclusive. Enabling OUTLINE disables the
hint_tablefeature of pg_hint_plan automatically.
Performance considerations
OUTLINE uses an internal high-concurrency cache to minimize overhead. Under standard Sysbench stress testing with OUTLINE enabled and active, Transactions Per Second (TPS) and Queries Per Second (QPS) decrease by approximately 1% to 2%.
Complete example
This example shows how to stabilize an execution plan that switches between two indexes depending on optimizer cost estimates.
1. Enable OUTLINE. See Enable the OUTLINE feature.
2. Create a test table and populate it:
CREATE TABLE t(a int, b int, PRIMARY KEY(a));
CREATE INDEX ON t(b);
INSERT INTO t SELECT i, i FROM generate_series(1, 100000) i;
ANALYZE t;3. Check the current plan. The optimizer chooses the index on column b:
EXPLAIN (costs off) SELECT * FROM t WHERE b = 1 AND a = 1;QUERY PLAN
-------------------------------
Index Scan using t_b_idx on t
Index Cond: (b = 1)
Filter: (a = 1)
(3 rows)4. Use a hint to force the primary key index and confirm it works:
EXPLAIN (costs off) /*+IndexScan(t t_pkey) */ SELECT * FROM t WHERE b = 1 AND a = 1;QUERY PLAN
------------------------------
Index Scan using t_pkey on t
Index Cond: (a = 1)
Filter: (b = 1)
(3 rows)5. Create an OUTLINE to lock in this plan:
CALL hint_plan.create_outline($$/*+IndexScan(t t_pkey) */ SELECT * FROM t WHERE b = 1 AND a = 1;$$);6. Verify the OUTLINE is applied. Run the original query without any inline hint—the primary key index is still used:
EXPLAIN (costs off) SELECT * FROM t WHERE b = 1 AND a = 1;QUERY PLAN
------------------------------
Index Scan using t_pkey on t
Index Cond: (a = 1)
Filter: (b = 1)
(3 rows)The OUTLINE also matches queries with different parameter values, added comments, or extra whitespace:
EXPLAIN (costs off) SELECT * -- comment
FROM t
WHERE b = 2 AND a = 4;QUERY PLAN
------------------------------
Index Scan using t_pkey on t
Index Cond: (a = 4)
Filter: (b = 2)
(3 rows)7. Check the OUTLINE status:
SELECT * FROM hint_plan.outlines_status; id | sql_id | hints | state | depends_rels | query_string | create_user | create_time | total_hints | calls
----+----------------------+---------------------+-------+--------------+------------------------------------------------------------------+-------------+----------------------------+---------------------+-------
1 | -3220256307655713529 | IndexScan(t t_pkey) | Y | {public.t} | /*+IndexScan(t t_pkey) */ SELECT * FROM t WHERE b = 1 AND a = 1; | postgres | 2024-11-11 11:24:44.063143 | IndexScan(t t_pkey) | 2
(1 row)8. When the OUTLINE is no longer needed, disable or delete it:
-- Disable
CALL hint_plan.disable_outline(1);
-- Or delete permanently
CALL hint_plan.del_outline(1);After disabling or deleting the OUTLINE, the optimizer reverts to its original plan:
EXPLAIN (costs off) SELECT * FROM t WHERE b = 1 AND a = 1;QUERY PLAN
-------------------------------
Index Scan using t_b_idx on t
Index Cond: (b = 1)
Filter: (a = 1)
(3 rows)Key concepts
| Term | Definition |
|---|---|
| HINT | An optimizer hint embedded in an SQL comment that influences how the optimizer generates an execution plan |
| OUTLINE | A binding that specifies which hint to apply to a class of SQL statements |
| OUTLINE DDL | Operations that modify OUTLINEs: create, delete, enable, and disable |
| SQL_ID | A unique identifier generated by the database to distinguish SQL classes. OUTLINE uses SQL_ID to match target statements |