Problem description
When I execute the WHERE $CONDITION ORDER BY $A LIMIT $N clause on an ApsaraDB RDS for MySQL instance, the execution efficiency is low when the value of $N is small. The execution plan indicates that the optimizer uses the $A index instead of the index that is related to the $CONDITION condition as the index.
Causes
The following execution plans are available for the preceding clause:
Execution plan 1 for the index that is related to the
$CONDITIONcondition:The system uses the index that is related to the
$CONDITIONcondition to scan data.The system uses the
$Aindex followingORDER BYto sort data.The system returns
$Ndata records.
Execution plan 2 for the
$Aindex followingORDER BY:The system uses the
$Aindex followingORDER BYto scan data.The system uses the
$CONDITIONcondition to filter data and returns$Ndata records.
For some SQL statements, the system returns a large amount of data based on the $CONDITION condition but the value of $N in LIMIT is small. In this case, the optimizer considers that the $A index is optimal. However, the execution efficiency of the SQL statements is low.
Solutions
The following solutions are provided to resolve the issue. Solution 1 is better than Solution 2 and Solution 3. You can select a solution based on the following description:
If your RDS instance runs MySQL 8.0 or MySQL 5.7, we recommend that you select Solution 1.
If your RDS instance runs MySQL 5.5 or MySQL 5.6, we recommend that you select Solution 3.
Solution 1: Use the statement outline feature to select an index
We recommend that you use this solution. The statement outline feature is provided by AliSQL to select indexes. ApsaraDB RDS for MySQL provides a built-in system table outline to store information such as SQL statements, characteristics, and hints. ApsaraDB RDS for MySQL also provides the DBMS_OUTLN toolkit. You can use the toolkit to define, delete, modify, and view rules. All rule information is stored in the outline table. If the SQL statement that is being executed matches the characteristics in the outline table, an execution plan is selected based on the optimizer hint and index hint.
If your RDS instance runs MySQL 8.0 or MySQL 5.7, you can use the statement outline feature to select indexes.
# 1. Use the statement outline feature to create a rule to specify the $C index.
call dbms_outln.add_index_outline('$SCHEMA', '', 1, '/* {USE|FORCE} INDEX ($C) */',
"SELECT $COL_1, $COL_2 FROM $TABLE_NAME WHERE $CONDITION ORDER BY $A LIMIT $N;");
#2. View rule matches.
dbms_outln.preview_outline('$SCHEMA','QUERY');
#3. View rule hits.
dbms_outln.show_outline();
#4. Delete a rule by rule ID.
dbms_outln.del_outline($Id);The statement outline feature supports the following types of hints provided by MySQL 8.0 and MySQL 5.7. For more information, see Statement outline.
Solution 2: Disable the optimizer behavior
If your RDS instance runs a version that is later than MySQL 5.7 with a minor engine version of 20211231 or later than MySQL 8.0 with a minor engine version of 20201231, you can set the prefer_ordering_index parameter for the optimizer to OFF to disable Execution Plan 2. MySQL 5.7 with a minor engine version of 20211231 corresponds to MySQL 5.7.33. MySQL 8.0 with a minor engine version of 20201231 corresponds to MySQL 8.0.21. You can execute the following statements to modify the parameter:
# 1. Configure optimizer options.
SET [GLOBAL|SESSION] optimizer_switch='prefer_ordering_index=OFF';
#2. Run the ORDER BY LIMIT query.
SELECT $COL_1, $COL_2 FROM $TABLE_NAME WHERE $CONDITION ORDER BY $A LIMIT $N;This solution affects the execution of subsequent global queries and sessions. If you use this solution to manage the optimizer behavior of different queries, you must reset optimizer_switch before each query, which is tedious. This solution is available only for specific versions.
The prefer_ordering_index parameter is a session-level variable. You can modify the parameter while your RDS instance is running. For more information about how to configure an optimizer, see Configure the optimizer for MySQL 5.7 and Configure the optimizer for MySQL 8.0.
Solution 3: Use index hints to select indexes
You can use an index hint to select an index. To modify the index, execute the following statement:
# 1. Forcefully use $C as the index.
SELECT $COL_1, $COL_2 FROM $TABLE_NAME {USE|FORCE} INDEX ($C) WHERE $CONDITION ORDER BY $A LIMIT $N;This solution requires tailored modification of SQL statements, which may cause a large number of frequent changes.
This solution enforces finer-grained control on optimizers compared with Solution 2. For more information about optimizer hints and index hints, see the following topics: