8.9 Controlling the Query Optimizer

MySQL provides optimizer control through system variables that affect how query plans are evaluated, switchable optimizations, optimizer and index hints, and the optimizer cost model.

 File: manual.info.tmp, Node: controlling-query-plan-evaluation, Next: switchable-optimizations, Prev: controlling-optimizer, Up: controlling-optimizer

8.9.1 Controlling Query Plan Evaluation

The task of the query optimizer is to find an optimal plan for executing an SQL query. Because the difference in performance between 'good' and 'bad' plans can be orders of magnitude (that is, seconds versus hours or even days), most query optimizers, including that of MySQL, perform a more or less exhaustive search for an optimal plan among all possible query evaluation plans. For join queries, the number of possible plans investigated by the MySQL optimizer grows exponentially with the number of tables referenced in a query. For small numbers of tables (typically less than 7 to 10) this is not a problem. However, when larger queries are submitted, the time spent in query optimization may easily become the major bottleneck in the server's performance.

A more flexible method for query optimization enables the user to control how exhaustive the optimizer is in its search for an optimal query evaluation plan. The general idea is that the fewer plans that are investigated by the optimizer, the less time it spends in compiling a query. On the other hand, because the optimizer skips some plans, it may miss finding an optimal plan.

The behavior of the optimizer with respect to the number of plans it evaluates can be controlled using two system variables:

 File: manual.info.tmp, Node: switchable-optimizations, Next: optimizer-hints, Prev: controlling-query-plan-evaluation, Up: controlling-optimizer

8.9.2 Switchable Optimizations

The 'optimizer_switch' system variable enables control over optimizer behavior. Its value is a set of flags, each of which has a value of 'on' or 'off' to indicate whether the corresponding optimizer behavior is enabled or disabled. This variable has global and session values and can be changed at runtime. The global default can be set at server startup.

To see the current set of optimizer flags, select the variable value:

 mysql> SELECT @@optimizer_switch\G
 *************************** 1. row ***************************
 @@optimizer_switch: index_merge=on,index_merge_union=on,
                     index_merge_sort_union=on,
                     index_merge_intersection=on,
                     engine_condition_pushdown=on,
                     index_condition_pushdown=on,
                     mrr=on,mrr_cost_based=on,
                     block_nested_loop=on,batched_key_access=off,
                     materialization=on,semijoin=on,loosescan=on,
                     firstmatch=on,duplicateweedout=on,
                     subquery_materialization_cost_based=on,
                     use_index_extensions=on,
                     condition_fanout_filter=on,derived_merge=on,
                     prefer_ordering_index=on

To change the value of 'optimizer_switch', assign a value consisting of a comma-separated list of one or more commands:

 SET [GLOBAL|SESSION] optimizer_switch='COMMAND[,COMMAND]...';

Each COMMAND value should have one of the forms shown in the following table.

Command Syntax Meaning

'default' Reset every optimization to its default value

'OPT_NAME=default' Set the named optimization to its default value

'OPT_NAME=off' Disable the named optimization

'OPT_NAME=on' Enable the named optimization

The order of the commands in the value does not matter, although the 'default' command is executed first if present. Setting an OPT_NAME flag to 'default' sets it to whichever of 'on' or 'off' is its default value. Specifying any given OPT_NAME more than once in the value is not permitted and causes an error. Any errors in the value cause the assignment to fail with an error, leaving the value of 'optimizer_switch' unchanged.

The following list describes the permissible OPT_NAME flag names, grouped by optimization strategy:

When you assign a value to 'optimizer_switch', flags that are not mentioned keep their current values. This makes it possible to enable or disable specific optimizer behaviors in a single statement without affecting other behaviors. The statement does not depend on what other optimizer flags exist and what their values are. Suppose that all Index Merge optimizations are enabled:

 mysql> SELECT @@optimizer_switch\G
 *************************** 1. row ***************************
 @@optimizer_switch: index_merge=on,index_merge_union=on,
                     index_merge_sort_union=on,
                     index_merge_intersection=on,
                     engine_condition_pushdown=on,
                     index_condition_pushdown=on,
                     mrr=on,mrr_cost_based=on,
                     block_nested_loop=on,batched_key_access=off,
                     materialization=on,semijoin=on,loosescan=on,
                     firstmatch=on,duplicateweedout=on,
                     subquery_materialization_cost_based=on,
                     use_index_extensions=on,
                     condition_fanout_filter=on,derived_merge=on,
                     prefer_ordering_index=on

If the server is using the Index Merge Union or Index Merge Sort-Union access methods for certain queries and you want to check whether the optimizer performs better without them, set the variable value like this:

 mysql> SET optimizer_switch='index_merge_union=off,index_merge_sort_union=off';

 mysql> SELECT @@optimizer_switch\G
 *************************** 1. row ***************************
 @@optimizer_switch: index_merge=on,index_merge_union=off,
                     index_merge_sort_union=off,
                     index_merge_intersection=on,
                     engine_condition_pushdown=on,
                     index_condition_pushdown=on,
                     mrr=on,mrr_cost_based=on,
                     block_nested_loop=on,batched_key_access=off,
                     materialization=on,semijoin=on,loosescan=on,
                     firstmatch=on,duplicateweedout=on,
                     subquery_materialization_cost_based=on,
                     use_index_extensions=on,
                     condition_fanout_filter=on,derived_merge=on,
                     prefer_ordering_index=on

 File: manual.info.tmp, Node: optimizer-hints, Next: index-hints, Prev: switchable-optimizations, Up: controlling-optimizer

8.9.3 Optimizer Hints

One means of control over optimizer strategies is to set the 'optimizer_switch' system variable (see *note switchable-optimizations::). Changes to this variable affect execution of all subsequent queries; to affect one query differently from another, it is necessary to change 'optimizer_switch' before each one.

another way to control the optimizer is by using optimizer hints, which can be specified within individual statements. Because optimizer hints apply on a per-statement basis, they provide finer control over statement execution plans than can be achieved using 'optimizer_switch'. For example, you can enable an optimization for one table in a statement and disable the optimization for a different table. Hints within a statement take precedence over 'optimizer_switch' flags.

Examples:

 SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1
   FROM t3 WHERE f1 > 30 AND f1 < 33;
 SELECT /*+ BKA(t1) NO_BKA(t2) */ * FROM t1 INNER JOIN t2 WHERE ...;
 SELECT /*+ NO_ICP(t1, t2) */ * FROM t1 INNER JOIN t2 WHERE ...;
 SELECT /*+ SEMIJOIN(FIRSTMATCH, LOOSESCAN) */ * FROM t1 ...;
 EXPLAIN SELECT /*+ NO_ICP(t1) */ * FROM t1 WHERE ...;

Note:

The note 'mysql': mysql. client by default strips comments from SQL statements sent to the server (including optimizer hints) until MySQL 5.7.7, when it was changed to pass optimizer hints to the server. To ensure that optimizer hints are not stripped if you are using an older version of the note 'mysql': mysql. client with a version of the server that understands optimizer hints, invoke *note 'mysql': mysql. with the '--comments' option.

Optimizer hints, described here, differ from index hints, described in *note index-hints::. Optimizer and index hints may be used separately or together.

Optimizer Hint Overview

Optimizer hints apply at different scope levels:

The following table summarizes the available optimizer hints, the optimizer strategies they affect, and the scope or scopes at which they apply. More details are given later.

Optimizer Hints Available

Hint Name Description Applicable Scopes

'BKA', 'NO_BKA'

Affects Batched Key Query block, table Access join
processing

'BNL', 'NO_BNL'

Affects Block Query block, table Nested-Loop join
processing

'MAX_EXECUTION_TIME'

Limits statement Global execution time

'MRR', 'NO_MRR'

Affects Multi-Range Table, index Read optimization

'NO_ICP'

Affects Index Table, index Condition Pushdown
optimization

'NO_RANGE_OPTIMIZATION'

Affects range Table, index optimization

'QB_NAME'

Assigns name to Query block query block

'SEMIJOIN', 'NO_SEMIJOIN'

semijoin strategies Query block

'SUBQUERY'

Affects Query block materialization, 'IN'-to-'EXISTS' subquery stratgies

Disabling an optimization prevents the optimizer from using it. Enabling an optimization means the optimizer is free to use the strategy if it applies to statement execution, not that the optimizer necessarily uses it.

Optimizer Hint Syntax

MySQL supports comments in SQL statements as described in note comments::. Optimizer hints must be specified within '/+ ... /' comments. That is, optimizer hints use a variant of '/ ... /' C-style comment syntax, with a '+' character following the '/' comment opening sequence. Examples:

 /*+ BKA(t1) */
 /*+ BNL(t1, t2) */
 /*+ NO_RANGE_OPTIMIZATION(t4 PRIMARY) */
 /*+ QB_NAME(qb2) */

Whitespace is permitted after the '+' character.

The parser recognizes optimizer hint comments after the initial keyword of note 'SELECT': select, note 'UPDATE': update, note 'INSERT': insert, note 'REPLACE': replace, and *note 'DELETE': delete. statements. Hints are permitted in these contexts:

A hint comment may contain multiple hints, but a query block cannot contain multiple hint comments. This is valid:

 SELECT /*+ BNL(t1) BKA(t2) */ ...

But this is invalid:

 SELECT /*+ BNL(t1) */ /* BKA(t2) */ ...

When a hint comment contains multiple hints, the possibility of duplicates and conflicts exists. The following general guidelines apply. For specific hint types, additional rules may apply, as indicated in the hint descriptions.

Query block names are identifiers and follow the usual rules about what names are valid and how to quote them (see *note identifiers::).

Hint names, query block names, and strategy names are not case-sensitive. References to table and index names follow the usual identifier case sensitivity rules (see *note identifier-case-sensitivity::).

Table-Level Optimizer Hints

Table-level hints affect use of the Block Nested-Loop (BNL) and Batched Key Access (BKA) join-processing algorithms (see *note bnl-bka-optimization::). These hint types apply to specific tables, or all tables in a query block.

Syntax of table-level hints:

 HINT_NAME([@QUERY_BLOCK_NAME] [TBL_NAME [, TBL_NAME] ...])
 HINT_NAME([TBL_NAME@QUERY_BLOCK_NAME [, TBL_NAME@QUERY_BLOCK_NAME] ...])

The syntax refers to these terms:

Examples:

 SELECT /*+ NO_BKA(t1, t2) */ t1.* FROM t1 INNER JOIN t2 INNER JOIN t3;
 SELECT /*+ NO_BNL() BKA(t1) */ t1.* FROM t1 INNER JOIN t2 INNER JOIN t3;

A table-level hint applies to tables that receive records from previous tables, not sender tables. Consider this statement:

 SELECT /*+ BNL(t2) */ FROM t1, t2;

If the optimizer chooses to process 't1' first, it applies a Block Nested-Loop join to 't2' by buffering the rows from 't1' before starting to read from 't2'. If the optimizer instead chooses to process 't2' first, the hint has no effect because 't2' is a sender table.

Index-Level Optimizer Hints

Index-level hints affect which index-processing strategies the optimizer uses for particular tables or indexes. These hint types affect use of Index Condition Pushdown (ICP), Multi-Range Read (MRR), and range optimizations (see *note select-optimization::).

Syntax of index-level hints:

 HINT_NAME([@QUERY_BLOCK_NAME] TBL_NAME [INDEX_NAME [, INDEX_NAME] ...])
 HINT_NAME(TBL_NAME@QUERY_BLOCK_NAME [INDEX_NAME [, INDEX_NAME] ...])

The syntax refers to these terms:

Examples:

 SELECT /*+ MRR(t1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3;
 SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1
   FROM t3 WHERE f1 > 30 AND f1 < 33;
 INSERT INTO t3(f1, f2, f3)
   (SELECT /*+ NO_ICP(t2) */ t2.f1, t2.f2, t2.f3 FROM t1,t2
    WHERE t1.f1=t2.f1 AND t2.f2 BETWEEN t1.f1
    AND t1.f2 AND t2.f2 + 1 >= t1.f1 + 1);

Subquery Optimizer Hints

Subquery hints affect whether to use semijoin transformations and which semijoin strategies to permit, and, when semijoins are not used, whether to use subquery materialization or 'IN'-to-'EXISTS' transformations. For more information about these optimizations, see *note subquery-optimization::.

Syntax of hints that affect semijoin strategies:

 HINT_NAME([@QUERY_BLOCK_NAME] [STRATEGY [, STRATEGY] ...])

The syntax refers to these terms:

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

If 'DUPSWEEDOUT' is disabled, on occasion the optimizer may generate a query plan that is far from optimal. This occurs due to heuristic pruning during greedy search, which can be avoided by setting 'optimizer_prune_level=0'.

Examples:

 SELECT /*+ NO_SEMIJOIN(@subq1 FIRSTMATCH, LOOSESCAN) */ * FROM t2
   WHERE t2.a IN (SELECT /*+ QB_NAME(subq1) */ a FROM t3);
 SELECT /*+ SEMIJOIN(@subq1 MATERIALIZATION, DUPSWEEDOUT) */ * FROM t2
   WHERE t2.a IN (SELECT /*+ QB_NAME(subq1) */ a FROM t3);

Syntax of hints that affect whether to use subquery materialization or 'IN'-to-'EXISTS' transformations:

 SUBQUERY([@QUERY_BLOCK_NAME] STRATEGY)

The hint name is always 'SUBQUERY'.

For 'SUBQUERY' hints, these STRATEGY values are permitted: 'INTOEXISTS', 'MATERIALIZATION'.

Examples:

 SELECT id, a IN (SELECT /*+ SUBQUERY(MATERIALIZATION) */ a FROM t1) FROM t2;
 SELECT * FROM t2 WHERE t2.a IN (SELECT /*+ SUBQUERY(INTOEXISTS) */ a FROM t1);

For semijoin and 'SUBQUERY' hints, a leading '@QUERY_BLOCK_NAME' specifies the query block to which the hint applies. If the hint includes no leading '@QUERY_BLOCK_NAME', the hint applies to the query block in which it occurs. To assign a name to a query block, see *note optimizer-hints-query-block-naming::.

If a hint comment contains multiple subquery hints, the first is used. If there are other following hints of that type, they produce a warning. Following hints of other types are silently ignored.

Statement Execution Time Optimizer Hints

The 'MAX_EXECUTION_TIME' hint is permitted only for *note 'SELECT': select. statements. It places a limit N (a timeout value in milliseconds) on how long a statement is permitted to execute before the server terminates it:

 MAX_EXECUTION_TIME(N)

Example with a timeout of 1 second (1000 milliseconds):

 SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM t1 INNER JOIN t2 WHERE ...

The 'MAX_EXECUTION_TIME(N)' hint sets a statement execution timeout of N milliseconds. If this option is absent or N is 0, the statement timeout established by the 'max_execution_time' system variable applies.

The 'MAX_EXECUTION_TIME' hint is applicable as follows:

Optimizer Hints for Naming Query Blocks

Table-level, index-level, and subquery optimizer hints permit specific query blocks to be named as part of their argument syntax. To create these names, use the 'QB_NAME' hint, which assigns a name to the query block in which it occurs:

 QB_NAME(NAME)

'QB_NAME' hints can be used to make explicit in a clear way which query blocks other hints apply to. They also permit all non-query block name hints to be specified within a single hint comment for easier understanding of complex statements. Consider the following statement:

 SELECT ...
   FROM (SELECT ...
   FROM (SELECT ... FROM ...)) ...

'QB_NAME' hints assign names to query blocks in the statement:

 SELECT /*+ QB_NAME(qb1) */ ...
   FROM (SELECT /*+ QB_NAME(qb2) */ ...
   FROM (SELECT /*+ QB_NAME(qb3) */ ... FROM ...)) ...

Then other hints can use those names to refer to the appropriate query blocks:

 SELECT /*+ QB_NAME(qb1) MRR(@qb1 t1) BKA(@qb2) NO_MRR(@qb3t1 idx1, id2) */ ...
   FROM (SELECT /*+ QB_NAME(qb2) */ ...
   FROM (SELECT /*+ QB_NAME(qb3) */ ... FROM ...)) ...

The resulting effect is as follows:

Query block names are identifiers and follow the usual rules about what names are valid and how to quote them (see *note identifiers::). For example, a query block name that contains spaces must be quoted, which can be done using backticks:

 SELECT /*+ BKA(@`my hint name`) */ ...
   FROM (SELECT /*+ QB_NAME(`my hint name`) */ ...) ...

If the 'ANSI_QUOTES' SQL mode is enabled, it is also possible to quote query block names within double quotation marks:

 SELECT /*+ BKA(@"my hint name") */ ...
   FROM (SELECT /*+ QB_NAME("my hint name") */ ...) ...

 File: manual.info.tmp, Node: index-hints, Next: cost-model, Prev: optimizer-hints, Up: controlling-optimizer

8.9.4 Index Hints

Index hints give the optimizer information about how to choose indexes during query processing. Index hints, described here, differ from optimizer hints, described in *note optimizer-hints::. Index and optimizer hints may be used separately or together.

Index hints apply to note 'SELECT': select. and note 'UPDATE': update. statements. They also work with multi-table *note 'DELETE': delete. statements, but not with single-table 'DELETE', as shown later in this section.

Index hints are specified following a table name. (For the general syntax for specifying tables in a note 'SELECT': select. statement, see note join::.) The syntax for referring to an individual table, including index hints, looks like this:

 TBL_NAME [[AS] ALIAS] [INDEX_HINT_LIST]

 INDEX_HINT_LIST:
     INDEX_HINT [INDEX_HINT] ...

 INDEX_HINT:
     USE {INDEX|KEY}
       [FOR {JOIN|ORDER BY|GROUP BY}] ([INDEX_LIST])
   | {IGNORE|FORCE} {INDEX|KEY}
       [FOR {JOIN|ORDER BY|GROUP BY}] (INDEX_LIST)

 INDEX_LIST:
     INDEX_NAME [, INDEX_NAME] ...

The 'USE INDEX (INDEX_LIST)' hint tells MySQL to use only one of the named indexes to find rows in the table. The alternative syntax 'IGNORE INDEX (INDEX_LIST)' tells MySQL to not use some particular index or indexes. These hints are useful if *note 'EXPLAIN': explain. shows that MySQL is using the wrong index from the list of possible indexes.

The 'FORCE INDEX' hint acts like 'USE INDEX (INDEX_LIST)', with the addition that a table scan is assumed to be very expensive. In other words, a table scan is used only if there is no way to use one of the named indexes to find rows in the table.

Each hint requires index names, not column names. To refer to a primary key, use the name 'PRIMARY'. To see the index names for a table, use the note 'SHOW INDEX': show-index. statement or the Information Schema note 'STATISTICS': information-schema-statistics-table. table.

An INDEX_NAME value need not be a full index name. It can be an unambiguous prefix of an index name. If a prefix is ambiguous, an error occurs.

Examples:

 SELECT * FROM table1 USE INDEX (col1_index,col2_index)
   WHERE col1=1 AND col2=2 AND col3=3;

 SELECT * FROM table1 IGNORE INDEX (col3_index)
   WHERE col1=1 AND col2=2 AND col3=3;

The syntax for index hints has the following characteristics:

If an index hint includes no 'FOR' clause, the scope of the hint is to apply to all parts of the statement. For example, this hint:

 IGNORE INDEX (i1)

is equivalent to this combination of hints:

 IGNORE INDEX FOR JOIN (i1)
 IGNORE INDEX FOR ORDER BY (i1)
 IGNORE INDEX FOR GROUP BY (i1)

In MySQL 5.0, hint scope with no 'FOR' clause was to apply only to row retrieval. To cause the server to use this older behavior when no 'FOR' clause is present, enable the 'old' system variable at server startup. Take care about enabling this variable in a replication setup. With statement-based binary logging, having different modes for the source and replicas might lead to replication errors.

When index hints are processed, they are collected in a single list by type ('USE', 'FORCE', 'IGNORE') and by scope ('FOR JOIN', 'FOR ORDER BY', 'FOR GROUP BY'). For example:

 SELECT * FROM t1
   USE INDEX () IGNORE INDEX (i2) USE INDEX (i1) USE INDEX (i2);

is equivalent to:

 SELECT * FROM t1
    USE INDEX (i1,i2) IGNORE INDEX (i2);

The index hints then are applied for each scope in the following order:

  1. '{USE|FORCE} INDEX' is applied if present. (If not, the optimizer-determined set of indexes is used.)

  2. 'IGNORE INDEX' is applied over the result of the previous step. For example, the following two queries are equivalent:

      SELECT * FROM t1 USE INDEX (i1) IGNORE INDEX (i2) USE INDEX (i2);
    
      SELECT * FROM t1 USE INDEX (i1);

For 'FULLTEXT' searches, index hints work as follows:

Index hints work with *note 'DELETE': delete. statements, but only if you use multi-table 'DELETE' syntax, as shown here:

 mysql> EXPLAIN DELETE FROM t1 USE INDEX(col2)
     -> WHERE col1 BETWEEN 1 AND 100 AND COL2 BETWEEN 1 AND 100\G
 ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that
 corresponds to your MySQL server version for the right syntax to use near 'use
 index(col2) where col1 between 1 and 100 and col2 between 1 and 100' at line 1

 mysql> EXPLAIN DELETE t1.* FROM t1 USE INDEX(col2)
     -> WHERE col1 BETWEEN 1 AND 100 AND COL2 BETWEEN 1 AND 100\G
 *************************** 1. row ***************************
            id: 1
   select_type: DELETE
         table: t1
    partitions: NULL
          type: range
 possible_keys: col2
           key: col2
       key_len: 5
           ref: NULL
          rows: 72
      filtered: 11.11
         Extra: Using where
 1 row in set, 1 warning (0.00 sec)

 File: manual.info.tmp, Node: cost-model, Prev: index-hints, Up: controlling-optimizer

8.9.5 The Optimizer Cost Model

To generate execution plans, the optimizer uses a cost model that is based on estimates of the cost of various operations that occur during query execution. The optimizer has a set of compiled-in default 'cost constants' available to it to make decisions regarding execution plans.

The optimizer also has a database of cost estimates to use during execution plan construction. These estimates are stored in the 'server_cost' and 'engine_cost' tables in the 'mysql' system database and are configurable at any time. The intent of these tables is to make it possible to easily adjust the cost estimates that the optimizer uses when it attempts to arrive at query execution plans.

Cost Model General Operation

The configurable optimizer cost model works like this:

The Cost Model Database

The optimizer cost model database consists of two tables in the 'mysql' system database that contain cost estimate information for operations that occur during query execution:

The 'server_cost' table contains these columns:

The primary key for the 'server_cost' table is the 'cost_name' column, so it is not possible to create multiple entries for any cost estimate.

The server recognizes these 'cost_name' values for the 'server_cost' table:

The 'engine_cost' table contains these columns:

The primary key for the 'engine_cost' table is a tuple comprising the ('cost_name', 'engine_name', 'device_type') columns, so it is not possible to create multiple entries for any combination of values in those columns.

The server recognizes these 'cost_name' values for the 'engine_cost' table:

If the 'io_block_read_cost' and 'memory_block_read_cost' values differ, the execution plan may change between two runs of the same query. Suppose that the cost for memory access is less than the cost for disk access. In that case, at server startup before data has been read into the buffer pool, you may get a different plan than after the query has been run because then the data is in memory.

Making Changes to the Cost Model Database

For DBAs who wish to change the cost model parameters from their defaults, try doubling or halving the value and measuring the effect.

Changes to the 'io_block_read_cost' and 'memory_block_read_cost' parameters are most likely to yield worthwhile results. These parameter values enable cost models for data access methods to take into account the costs of reading information from different sources; that is, the cost of reading information from disk versus reading information already in a memory buffer. For example, all other things being equal, setting 'io_block_read_cost' to a value larger than 'memory_block_read_cost' causes the optimizer to prefer query plans that read information already held in memory to plans that must read from disk.

This example shows how to change the default value for 'io_block_read_cost':

 UPDATE mysql.engine_cost
   SET cost_value = 2.0
   WHERE cost_name = 'io_block_read_cost';
 FLUSH OPTIMIZER_COSTS;

This example shows how to change the value of 'io_block_read_cost' only for the 'InnoDB' storage engine:

 INSERT INTO mysql.engine_cost
   VALUES ('InnoDB', 0, 'io_block_read_cost', 3.0,
   CURRENT_TIMESTAMP, 'Using a slower disk for InnoDB');
 FLUSH OPTIMIZER_COSTS;

 File: manual.info.tmp, Node: buffering-caching, Next: locking-issues, Prev: controlling-optimizer, Up: optimization