Expanded New-Style Optimizer Hints

circle-info

New-style optimizer hints were introduced in MariaDB 12.0 and 12.1.

Description

In order to control optimizer choices of query plans, one can use optimizer_switch, join cache level and other system variables. However, these variables affect execution of all queries but not some specific ones. To get more granular control, one can use optimizer hints.

Optimizer hints allow to control execution plans on a per-query basis. Technically, they are specifically formatted comments embedded right into the query text. For example:

SELECT /*+ JOIN_ORDER(t2, t1) NO_INDEX(t2)*/ t1.* FROM t1, t2 ... ;
SELECT /*+ NO_SEMIJOIN() */ * FROM t1 WHERE t1.a IN (...);
SELECT /*+ MERGE(dt1) */ * FROM (SELECT * FROM t1) AS dt1;
DELETE /*+ NO_BNL(t2@qb1) */ * FROM t1 WHERE a IN (SELECT /*+ QB_NAME(qb1) */ .. );
UPDATE /*+ NO_RANGE_OPTIMIZATION(t1 PRIMARY) */  * FROM t1 ...;

Syntax

Hints sequence starts with /*+ and ends with */. There can be an arbitrary number of hints in the sequence, separated by spaces. Hints are recognized by the parser if they follow the initial keyword SELECT, UPDATE, DELETE. Each query block can have its own set of hints, for example:

SELECT /*+ ... */ ... FROM t1 WHERE a IN (SELECT /*+ ... */ ...);
UPDATE /*+ ... */ ... WHERE a IN (SELECT /*+ ... */ ... );
SELECT /*+ ... */ ... UNION ALL SELECT /*+ ... */ ...;

INSERT ... SELECT statements support hints only at the SELECT part, not at the INSERT part:

INSERT INTO ... SELECT /*+ ... */ ...;

A single query block may have multiple hints in a single hint sequence, for example:

but multiple sequences are not supported:

Incorrect syntax, duplication of hints or other inconsistencies produce warnings:

Hints that were not rejected as invalid/conflicting/duplicated are visible in the output of EXPLAIN EXTENDED, in section "Warnings":

General notes

  • If a table has an alias, hints must refer to the alias, not the table name.

  • Table names in hints cannot be qualified with schema names.

  • Hints may be specified within VIEW's during their creation, and they are applied locally within that VIEW's scope. For example:

The hints are then visible in the output of SHOW CREATE VIEW.

Hint Hierarchy

Hints can apply at different levels:

  • global - the hints affect the whole query;

  • query-block-level - the hints affect a certain query block of the query;

  • table-level - the hints affect certain tables;

  • index-level - the hints affect particular indexes of tables.

Available Optimizer Hints

This table provides an overview of optimizer hints supported in MariaDB, showing which optimizations each hint controls and at what level (global, query block, table, or index) they can be applied.

Hint Name
Affected Optimization
Applicable Scopes

Batched key access join

Query block, Table

Block nested-loop join

Query block, Table

Condition pushdown for derived tables

Query block, Table

Use of indexes for GROUP BY operations

Table, Index

Use of indexes

Table, Index

Index merge

Table, Index

Straight join order

Query block

Use of indexes for data access

Index

Join order

Table

Join order for first tables

Table

Join order for last tables

Table

Query execution time limit

Global

Derived table/CTE merging

Query block, Table

Multi-Range Read

Table, Index

Index Condition Pushdown

Table, Index

Range optimization

Table, Index

Use of indexes for sorting

Table, Index

Assigns name to query block

Query block

Rowid filtering

Table, Index

Semi-join optimization

Query block

Lateral derived / split materialization

Table

Subquery transformation

Query block

Syntax of global Hints

Such hints affect the whole query.

Syntax

Currently, there is only one global hint:

Syntax of Query-Block Level Hints

These hints affect a certain query block of the query.

Syntax

Examples

This statement contains one query block (since its name is not specified explicitly, this query block is assigned an implicit name select#1). The NO_BNL() hint does not specify a query block name, so it is applied to the query block where the hint is specified. The hint disables block nested loop join for any tables of this query block.

This statement contains two query blocks:

  • the topmost one which does not have an explicit name;

  • the one corresponding to derived table dt which is assigned an explicit name qb1 with the use of QB_NAME hint.

Here the BKA(@qb1) hint addresses query block name qb1. The hint enables batched key access join for tables of this query block.

This syntax is equivalent to:

See query block naming for more information.

Syntax of Table-Level Hints

These hints affect certain tables of the statement.

There are two syntax variants of table-level hints: to affect tables from the same query block, and to affect tables from different query blocks.

Tables from the same query block

This variant addresses some or all tables of a particular query block:

Both @query_block_name and the list of tbl_name's are optional. If @query_block_name is not specified, the hint applies to tables of the query block the hint is added to. If no tables are specified in the list, the hint affects the whole query block and effectively becomes a query-block-level hint.

Examples

The hint disables merging of derived table dt into the upper SELECT. More information about this hint can be found here.

If a user wants to disable merging of both dt and dt2, they can mention both derived table names in the hint body:

Alternatively, since there are no more derived tables in the statement besides dt and dt2, the same effect can be achieved with the query-block level variant of the hint:

Here the effect of the hint is applied to the scope of the main query block.

Now let's consider a more complicated example when a user has a statement with two derived tables one of which is nested into another:

If a user wants to disable merging of the inner derived table dt_inner, there are three ways of doing that:

  • assign an explicit name to the query block that the inner derived table belongs to, and address dt_inner with a hint from the topmost query block:

If there were more derived tables in inner_qb_name query block to address, they all should have been mentioned in the hint body, for example: NO_MERGE(@inner_qb_name dt_inner, dt_inner2, dt_inner3)

  • place the hint right into the inner query block:

Tables from different query blocks

This variant of the syntax addresses tables from different query blocks:

For example, a user wants to disable batched key access join for table t2 from derived query dt and for table t3 from the topmost query block. They can mention both table names in the hint body as follows:

If they run EXPLAIN EXTENDED for this query, they will see the hint applied to both tables from the hint body:

select#1 here is the implicit system name of the topmost query block (see more about this at query block naming).

In fact, the user can add the hints to the query in the same way as it is displayed above:

or

It is also possible to assign a name to the topmost query block and refer each table by the explicit block name:

or use implicit system names of query blocks:

Syntax of Index-Level Hints

These hints affect the use of all or certain indexes of a table.

Possible syntax variants are:

table_name/table_name@query_block is necessary while the list of index_name's can be omitted. In the latter case the hint applies at the table level. However, index-level hints cannot be elevated to the scope of a query block, i.e., syntax hint_name(@query_block) is not allowed.

Let us say a user has a table:

and the optimizer chooses range scan of index i_a for the query

If the user wants to enforce the optimizer employ full scan of t1, they can add NO_INDEX hint:

If for some reason the optimizer chooses a suboptimal index when there is a more efficient one (say, i_ab), the user can force the optimizer to choose the preferred index:

While INDEX/NO_INDEX hints control the use of indexes for any operations, GROUP_INDEX/NO_GROUP_INDEX, JOIN_INDEX/NO_JOIN_INDEX and ORDER_INDEX/NO_ORDER_INDEX provide more fine-grained control.

Table-level hints

Supported variants of syntax:

See Syntax of query-block and table level hints for more information.

* BKA(), NO_BKA()

circle-info

Available from MariaDB 12.0.

Enable or disable batched key access join algorithm.

* BNL(), NO_BNL()

circle-info

Available from MariaDB 12.0.

Enable or disable block nested-loop join algorithm.

These hints override the settings of system variables used to manage usage of block-based join algorithms.

Examples

join_cache_level = 2 limits the use of block-based join algorithms to flat or incremental BNL (BKA joins are not allowed). However, with the BKA(t13) hint tables t12 and t13 will be joined using the BKA hashed algorithm.

The same works in the opposite direction too. Say we have a very permissive setting join_cache_level = 8 that allows all block-based join algorithms. However, with the NO_BKA(t13) hint tables t12 and t13 will be joined using the BNL hashed algorithm.

These hints can be applied either to particular tables or to the whole query-block. When applied to particular tables, the hint body should mention tables that receive data from previous tables, i.e. which appear later in the join order. That means the hints in examples above will work if the optimizer joins tables t12 and t13 in that order. If the optimizer joins tables in the reverse order, the hints should mention t12 in their body instead.

* DERIVED_CONDITION_PUSHDOWN(), NO_DERIVED_CONDITION_PUSHDOWN()

circle-info

Available from MariaDB 12.1.

These hints enable or disable the Condition Pushdown into Derived Table Optimization, effectively overriding the optimizer switch setting condition_pushdown_for_derived=[on|off]. They apply to derived tables, views and CTEs of the query.

Examples

Here the hint disables pushing conditions (max_c > 300 or max_c < 135) into the view v1.

In the next example the optimization is disabled by the optimizer switch setting, however the hint allows pushing conditions into the view v1 inside cte:

Example with pushing down conditions into a derived table:

* MERGE(), NO_MERGE()

circle-info

Available from MariaDB 12.1.

These hints enable or disable the Derived Table Merge Optimization, effectively overriding the optimizer switch setting derived_merge=[on|off].

Examples

Let's say there is a statement:

By default the derived table dt gets merged into the main SELECT, so the output of EXPLAIN displays only one line.

However, if the user does not want dt to be merged, they can add NO_MERGE() hint:

Now the output displays two lines indicating that dt has not been merged but materialized instead. The same effect could be achieved by applying query-block level variant of the hint: /*+ NO_MERGE()*/ (however, the hint would affect not only dt but all other derived tables of the query block, if there were any).

The hints can be applied to CTE's too:

However, they do not have effect on merging of views.

* SPLIT_MATERIALIZED(), NO_SPLIT_MATERIALIZED()

circle-info

Available from MariaDB 12.0.

These hints control the Split-materialized optimization also known as Lateral Derived optimization.

Let's look at the statement which will employ the lateral derived optimization by default:

select_type = LATERAL DERIVED at the bottom line of EXPLAIN output indicates the optimization has been applied.

If the user applies the hint, the optimization will be disabled and there will be no more LATERAL DERIVED in the output:

As with other hints, users can use SPLIT_MATERIALIZED() to force the optimization when it is not chosen by the cost-based optimizer or disabled by default ( optimizer_switch='split_materialized=off' ).

Index-level hints

Overview

These hints follow the syntax of index-level hints which includes two variants:

They enable or disable the specified indexes for index scans for specific operations. The following rules apply:

  • for enabling (or whitelisting) hints (GROUP_INDEX, JOIN_INDEX, INDEX, ORDER_INDEX):

    • if the hint body includes only a table name and no index names, the optimizer is free to choose any applicable index of that table;

    • if the body includes both a table name and one or more index names, the optimizer will only consider the specified indexes for that table;

  • for disabling (or blacklisting) hints (NO_GROUP_INDEX, NO_JOIN_INDEX, NO_INDEX, NO_ORDER_INDEX):

    • if the hint body includes only a table name and no index names, the optimizer will not use any indexes for that table;

    • if the body includes both a table name and one or more index names, the optimizer will not use the specified indexes for that table but can use any other not listed.

* GROUP_INDEX(), NO_GROUP_INDEX()

circle-info

Available from MariaDB 12.1.

These hints enable or disable the specified indexes for index scans for GROUP BY operations but do not affect other operations: access to table data and sorting. Equivalent to old-style hints FORCE INDEX FOR GROUP BY and IGNORE INDEX FOR GROUP BY.

Examples

* JOIN_INDEX(), NO_JOIN_INDEX()

circle-info

Available from MariaDB 12.1.

Enable or disable the specified indexes for an access method (range, ref, etc.). Equivalent to old-style hints FORCE INDEX FOR JOIN and IGNORE INDEX FOR JOIN.

Example

* INDEX(), NO_INDEX()

circle-info

Available from MariaDB 12.1.

Enable or disable the specified indexes for all scopes (access to table data, grouping and sorting). Equivalent to old-style hints FORCE INDEX and IGNORE INDEX.

Examples

* ORDER_INDEX(), NO_ORDER_INDEX()

circle-info

Available from MariaDB 12.1.

These hints enable or disable the specified indexes for sorting rows but do not affect other operations: access to table data and grouping. Equivalent to old-style hints FORCE INDEX FOR ORDER BY and IGNORE INDEX FOR ORDER BY.

Example

* Index hints combination

Multiple index hints can be combined in a single query unless they conflict with each other. For example:

Here the hints do not conflict with each other because they target different operations.

A conflict may happen when the same class of hints is applied to a single table, for example:

or when an umbrella hint INDEX/NO_INDEX is specified:

Here the warning will be generated:

because INDEX hint is essentially a combination of JOIN_INDEX|GROUP_INDEX|ORDER_INDEX.

If a single SQL query includes both old-style and new-style index hints, old-style hints are silently ignored, for example:

New-style INDEX(t1 i_a) has effect while old-style IGNORE INDEX(i_a) does not.

* INDEX_MERGE(), NO_INDEX_MERGE()

circle-info

This hint is available from MariaDB 12.2.

The INDEX_MERGE and NO_INDEX_MERGE optimizer hints provide granular control over the optimizer's use of index merge strategies. They allow users to override the optimizer's cost-based calculations and global switch settings, to force or prevent the merging of indexes for specific tables.

Syntax

Behavior

The hints operate by modifying the set of keys the optimizer considers for merge operations. The specific behavior depends on whether specific index keys are provided within the hint.

INDEX_MERGE Hint

This hint instructs the optimizer to employ an index merge strategy.

  • Without arguments: When specified as INDEX_MERGE(tbl), the optimizer considers all available keys for that table and selects the cheapest index merge combination.

  • With specific keys: When specified with keys, for instance, INDEX_MERGE(tbl key1, key2), the optimizer considers only the listed keys for the merge operation. All other keys are excluded from consideration for index merging.

circle-info

The INDEX_MERGE hint overrides the global optimizer_switch. Even if a specific strategy (such as index_merge_intersection) is disabled globally, the hint forces the optimizer to attempt the strategy using the specified keys.

NO_INDEX_MERGE Hint

This hint instructs the optimizer to avoid index merge strategies.

  • Without arguments: When specified as NO_INDEX_MERGE(tbl), index merge optimizations are completely disabled for the specified table.

  • With specific keys: When specified with keys, for instance, NO_INDEX_MERGE(tbl key1), the listed keys are excluded from consideration. The optimizer may still perform a merge using other available keys. However, if excluding the listed keys leaves insufficient row-ordered retrieval (ROR) scans available, no merge is performed.

Algorithm Selection and Limitations

While these hints control which keys are candidates for merging, they do not directly dictate the specific merge algorithm (Intersection, Union, or Sort-Union).

  • Indirect Control: You can influence the strategy indirectly by combining these hints with optimizer_switch settings, but specific algorithm selection is not guaranteed.

  • Invalid Hints: If a hint directs the optimizer to use specific indexes, but those indexes do not provide sufficient ROR scans to form a valid plan, the server is unable to honor the hint. In this scenario, the server emits a warning.

Examples

In the following examples, the index_merge_intersection switch is globally disabled. However, the INDEX_MERGE hint forces the optimizer to consider specific keys (f2 and f4), resulting in an intersection strategy.

You can see that we disable intersection with NO_INDEX_MERGE for the following query and the behavior reflects in the EXPLAIN output. The query after that shows with the hint enabling merge–an intersection of f3,f4 is used. In the last example, a different intersection is used: f3,PRIMARY.

No intersection (no merged indexes):

Intersection of keys f3, f4:

Intersection of keys PRIMARY, f3:

* NO_ICP()

circle-info

This hint is available from MariaDB 12.0.

Disables Index Condition Pushdown for a specified table or a subset of its indexes. Effectively overrides the optimizer_switch setting index_condition_pushdown=on.

Examples

* NO_RANGE_OPTIMIZATION()

circle-info

This hint is available from MariaDB 12.0.

An index-level hint that disables range optimization for certain index(es) or a whole table. There is no corresponding optimizer switch setting for this optimization.

Examples

* MRR(), NO_MRR()

circle-info

Available from MariaDB 12.0.

These hints allow to control the multi-range read optimization, effectively overriding the optimizer switch setting mrr=[on|off].

Examples

* ROWID_FILTER(), NO_ROWID_FILTER()

circle-info

Available from MariaDB 12.1.

Allows to control the ROWID filter optimization. Effectively they override the optimizer switch setting rowid_filter=[on|off].

  • Rules for ROWID_FILTER() hint:

    • if the hint body includes only a table name and no index names, the optimizer is free to choose any indexes applicable for rowid-filtering optimization;

    • if the body includes both a table name and one or more index names, the optimizer will only consider the specified indexes for rowid-filtering;

    • the hint forces the use of rowid-filtering (if applicable) despite being less efficient than not using the optimization (from the optimizer's point of view);

  • Rules for NO_ROWID_FILTER() hint:

    • if the hint body includes only a table name and no index names, the optimizer will not use any indexes for rowid-filtering;

    • if the body includes both a table name and one or more index names, the optimizer will not use the specified indexes for rowid-filtering but can use any other not listed.

Examples

Interaction with other index hints

If [NO_]INDEX(), [NO_]JOIN_INDEX() hints are specified for the same table, then only indexes whitelisted by INDEX() or JOIN_INDEX() can be considered for rowid filters. ROWID_FILTER( hint cannot force the use of indexes that are disabled by NO_INDEX() / NO_JOIN_INDEX() or omitted from INDEX() / JOIN_INDEX(). For example, if one index should be used for data access and another for a rowid filter, the filter index must be mentioned in both hints:

Join Order hints

circle-info

Available from MariaDB 12.0.

Overview

These hints allow to control the order in which tables of a query are joined.

Generally, these hints follow the syntax rules of table-level hints with some important differences.

Syntax of the JOIN_FIXED_ORDER hint:

In contrast to other table-level hints, JOIN_FIXED_ORDER does not allow specifying table names in the hint body.

Syntax variants of other join-order hints:

Here the query block name may be omitted, but at least one table name must be specified.

* JOIN_FIXED_ORDER([@query_block_name])

Forces the optimizer to join tables using the order in which they appear in the FROM clause. This is the same as specifying SELECT STRAIGHT_JOIN.

Examples

* JOIN_ORDER()

Instructs the optimizer to join tables using the specified table order. The hint applies to the named tables. The optimizer may place tables that are not named anywhere in the join order, including between specified tables.

* JOIN_PREFIX()

Instructs the optimizer to join tables using the specified table order for the first tables of the join execution plan. The hint applies to the named tables. The optimizer places all other tables after the named tables.

* JOIN_SUFFIX()

Instructs the optimizer to join tables using the specified table order for the last tables of the join execution plan. The hint applies to the named tables. The optimizer places all other tables before the named tables.

Example:

Subquery hints

circle-info

Available from MariaDB 12.0.

Overview

Subquery hints determine:

* SEMIJOIN(), NO_SEMIJOIN()

Syntax

  • hint_name: SEMIJOIN or NO_SEMIJOIN.

  • strategy: name of the strategy to be enabled (in case of SEMIJOIN hint) or disabled (in case of NO_SEMIJOIN hint). The following strategy names are permitted: DUPSWEEDOUT, FIRSTMATCH, LOOSESCAN, MATERIALIZATION.

For SEMIJOIN hints, if no strategies are named, semi-join is used based on the strategies enabled according to the optimizer_switch system variable, if possible. If strategies are named, but inapplicable for the statement, DUPSWEEDOUT is used.

For NO_SEMIJOIN hints, semi-join is not used if no strategies are named. If named strategies rule out all applicable strategies for the statement, DUPSWEEDOUT is used.

If a subquery is nested within another, and both are merged into a semi-join of an outer query, any specification of semi-join strategies for the innermost query are ignored. SEMIJOIN and NO_SEMIJOIN hints can still be used to enable or disable semi-join transformations for such nested subqueries.

Examples

* SUBQUERY()

Syntax

  • strategy: allowed values are INTOEXISTS and MATERIALIZATION.

Examples

* MAX_EXECUTION_TIME()

circle-info

Available from MariaDB 12.0.

Global-level hint to limit query execution time

A query that doesn't finish in the time specified will be aborted with an error.

However, if @@max_statement_time system variable is set, the hint will be ignored and a warning produced. Note that this contradicts the stated principle that "new-style hints are more specific than server variable settings, so they override the server variable settings".

Query Block Naming

Optimizer hints can address certain named query blocks within the query. Query block is either a top-level SELECT/UPDATE/DELETE statement or a subquery within the statement.

There are three ways to address query blocks:

  • by an explicit name assigned using QB_NAME() hint

  • by implicit name based on position of the query block in the query

  • by implicit name based on alias of derived table, view or CTE

Explicit query block names

  • QB_NAME(query_block_name) hint is used to assign a name to a query block in which this hint is placed.

Examples

Assigning a name to a top-level SELECT statement:

Assigning a name to a subquery:

Assigning names to multiple query blocks:

The assigned name can then be used to refer to the query block in optimizer hints, for example:

The scope of a query block name is the whole statement. It is invalid to use the same name for multiple query blocks. You can refer to the query block "down into subquery", "down into derived table", "up to the parent" and "to a right sibling in the UNION". You cannot refer "to a left sibling in a UNION".

Explicit query block names with path

circle-info

Available from MariaDB 13.0.

  • QB_NAME(query_block_name, query_block_path) hint is used to assign names to query blocks nested within views, derived tables, and CTEs

Syntax

where

query_block_path is similar to a path in filesystem in some sense. Each next element descends further into the query blocks structure. Path elements are separated with dots (.).

Each path element can be either a:

  • View/derived table/CTE name

  • Select number in the current query block, such as @SEL_1, @SEL_2, etc.

  • Combination of view/derived table/CTE name and select number, such as v1@SEL_1, dt1@SEL_2, etc.

Examples

Path consisting of two elements:

Views and derived tables may be nested on multiple levels, for example:

Limitations

  • Only SELECT statements support QB names with path. DML operations (UPDATE, DELETE, INSERT) do not support them

  • QB names with path are not supported inside view definitions (CREATE VIEW ...)

Implicit names based on position

circle-info

Available from MariaDB 12.2.

Besides the given name, any query block has an implicit name select_#n (where #n stands for a number). You can see these numbers in the column id of the EXPLAIN output:

Limitations

  • The positional numbers of query blocks are not stable and can change if the query is modified or its execution plan changes (for example, due to statistics re-collection). If a more stable query block addressing is needed then explicit naming is probably a better choice.

  • Implicit names based on position are not supported inside view definitions (CREATE VIEW ...).

Implicit names based on aliases

circle-info

Available from MariaDB 13.0

It is possible to address a query block corresponding to a derived table, view or CTE using its name or an alias in the query.

Examples

Limitations

  • Only SELECT statements support implicit QB names based on aliases. DML operations (UPDATE, DELETE, INSERT) do not support them.

  • Implicit names based on aliases are not supported inside view definitions (CREATE VIEW ...).

Last updated

Was this helpful?