Before MariaDB 11.0, the MariaDB Query optimizer used a 'basic cost' of 1 for:
There were some smaller costs:
The above costs are reasonable for finding out the best index to use. However, they where not good for finding out if we should use a table scan, index scan or range lookup. The cost for the different engines were not properly calibrated.
In MariaDB 11.0 we have fixed the above shortcomings by changing the basic cost for 'storage engine operations' to be 1 millisecond. This means that for most queries the query cost (LAST_QUERY_COST) should be close (or at least proportional) to the time the server is spending in the storage engine + join_cache + sorting.
Note that the user level costs are in microseconds (as milliseconds would have so many zero's that it makes it hard to compare values).
The engine costs have also been separated into smaller parts to make things more accurate.
The "disk"-read cost now assumes a mid level SSD disk with 400MB/second. This can be changed by the end user by modifying OPTIMIZER_DISK_READ_COST.
All engine specific costs are visible in information_schema.optimizer_costs.
For example:
The "default" cost for an engine can be found with:
select * from information_schema.optimizer_costs where engine="default"\G
*************************** 1. row ***************************
ENGINE: default
OPTIMIZER_DISK_READ_COST: 10.240000
OPTIMIZER_INDEX_BLOCK_COPY_COST: 0.035600
OPTIMIZER_KEY_COMPARE_COST: 0.011361
OPTIMIZER_KEY_COPY_COST: 0.015685
OPTIMIZER_KEY_LOOKUP_COST: 0.435777
OPTIMIZER_KEY_NEXT_FIND_COST: 0.082347
OPTIMIZER_DISK_READ_RATIO: 0.020000
OPTIMIZER_ROW_COPY_COST: 0.060866
OPTIMIZER_ROW_LOOKUP_COST: 0.130839
OPTIMIZER_ROW_NEXT_FIND_COST: 0.045916
OPTIMIZER_ROWID_COMPARE_COST: 0.002653
OPTIMIZER_ROWID_COPY_COST: 0.002653
The above costs are the default (base) for all engines and should be reasonable for engines that does not have a clustered index (like MyISAM, Aria etc). The default costs can be changed by specifying just the cost as an argument, like mariadbd --optimizer-disk-read-cost=20 or from SQL: set global optimizer_disk_read_cost=20. An engine specific cost can be tuned by prefixing the cost with the engine name, like set global innodb.optimizer_disk_read_cost=20.
An engine can tune some or all of the above cost in the storage engine interface. Here follows the cost for the InnoDB storage engine.
select * from information_schema.optimizer_costs where engine="innodb"\G
*************************** 1. row ***************************
ENGINE: InnoDB
OPTIMIZER_DISK_READ_COST: 10.240000
OPTIMIZER_INDEX_BLOCK_COPY_COST: 0.035600
OPTIMIZER_KEY_COMPARE_COST: 0.011361
OPTIMIZER_KEY_COPY_COST: 0.015685
OPTIMIZER_KEY_LOOKUP_COST: 0.791120
OPTIMIZER_KEY_NEXT_FIND_COST: 0.099000
OPTIMIZER_DISK_READ_RATIO: 0.020000
OPTIMIZER_ROW_COPY_COST: 0.060870
OPTIMIZER_ROW_LOOKUP_COST: 0.765970
OPTIMIZER_ROW_NEXT_FIND_COST: 0.070130
OPTIMIZER_ROWID_COMPARE_COST: 0.002653
OPTIMIZER_ROWID_COPY_COST: 0.002653
As can be seen, the ROW_LOOKUP_COST is close to the KEY_LOOKUP_COST, which is because InnoDB has clustered primary key indexes and is using it to find the row from a secondary index.
Some engines, like HEAP/MEMORY implement their own cost functions as different indexes in the same engine can have different costs. This is why some of the cost numbers for these engines are 0.
There are also some SQL level costs that are independent of the storage engine:
select * from information_schema.global_variables where variable_name like "%where%cost%" or variable_name like "%scan%cost%"; +---------------------------+----------------+ | VARIABLE_NAME | VARIABLE_VALUE | +---------------------------+----------------+ | OPTIMIZER_SCAN_SETUP_COST | 10.000000 | | OPTIMIZER_WHERE_COST | 0.032000 | +---------------------------+----------------+
Time and cost are quite interchangeable in the new cost model. Below we will use cost for most things, except for OPTIMIZER_DISK_READ_COST as one should use published/tested timings for the SSD/harddisk if one wants to change the value..
| Variable | Type | Description |
|---|---|---|
OPTIMIZER_DISK_READ_COST |
Engine | Time in microseconds to read a 4K block from a disk/SSD. The default is set for a 400MB/second SSD |
OPTIMIZER_INDEX_BLOCK_COPY_COST |
Engine | Cost to lock and a copy a block from the global cache to a local cache. This cost is added for every block accessed, independent of whether they are cached or not |
OPTIMIZER_KEY_COMPARE_COST |
Engine | Cost to compare two keys |
OPTIMIZER_KEY_COPY_COST |
Engine | Cost to copy a key from the index to a local buffer as part of searching for a key |
OPTIMIZER_KEY_LOOKUP_COST |
Engine | Cost to find a key entry in the index (index read) |
OPTIMIZER_KEY_NEXT_FIND_COST |
Engine | Cost to find the next key in the index (index next) |
OPTIMIZER_DISK_READ_RATIO |
Engine | The ratio of BLOCK_NOT_IN_CACHE/CACHE_READS. The cost of disk usage is calculated as estimated_blocks * OPTIMIZER_DISK_READ_RATIO * . A value of 0 means that all blocks are always in the cache. A value of 1 means that a block is never in the cache |
OPTIMIZER_ROW_COPY_COST |
Engine | Cost of copying a row to a local buffer. Should be slightly more than OPTIMIZER_KEY_COPY_COST
|
OPTIMIZER_ROW_LOOKUP_COST |
Engine | Cost to find a row based on the rowid (Rowid is stored in the index together with the key) |
OPTIMIZER_ROW_NEXT_FIND_COST |
Engine | Cost of finding the next row |
OPTIMIZER_ROWID_COMPARE_COST |
Engine | Cost of comparing two rowids |
OPTIMIZER_ROWID_COPY_COST |
Engine | Cost of copying a rowid from the index |
OPTIMIZER_SCAN_SETUP_COST |
Session | Cost of starting a table or index scan. This has a low value to encourage the optimizer to use index lookup also tables with very few rows |
OPTIMIZER_WHERE_COST |
Session | Cost to execute the WHERE clause for every found row. Increasing this variable will encourage the optimizer to find plans which read fewer rows |
More information of the costs and how they were calculated can be found in the Docs/optimizer_costs.txt file in the MariaDB Source distributions.
If we would count all lookups in t2, there would be 1 million lookups. If this would be the case, the optimizer would choose to use a join cache on the rows in t1 and do a table scan over t2.
ORDER BY/GROUP BY queries. A lot of rule-based cost has been changed to be cost-based:
ORDER BY/GROUP BY were only partly cost-based before. ORDER BY/GROUP BY in sub queries (instead of filesort) UNION can now create a distinct key (instead of a key with duplicates) to speed up key accesses. WHERE key_part_1 = 1 and key_part_2 < 10
RANGE over both key parts instead of using lookups on key_part_1. EXPLAIN does not report "Using index" for scans using a clustered primary key as technically this a table scan. The new, improved optimizer should be able to find a better plan
WHERE key between 1 and 1000 -- Table has values 1-2000
FORCE INDEX to get a good plan. ORDER BY / GROUP BY that could be resolved via indexes. All engine and “SQL level” cost variables can be changed via MariaDB startup options, in configuration files or dynamically using SQL.
[mariadbd] # Archive is using a hard disk (typical seek is 8-10 ms) archive.OPTIMIZER_DISK_READ_COST=8000 # All other engines are using an SSD. OPTIMIZER_DISK_READ_COST=10.240000
# Tell optimizer to find a plan with as few accepted rows as possible SET SESSION OPTIMIZER_WHERE_COST=1.0; # Inform the optimizer that InnoDB buffer pool has a 80% hit rate SET GLOBAL innodb.OPTIMIZER_DISK_READ_RATIO=0.20;
GLOBAL while other costs can also be SESSION. OPTIMIZER_WHERE_COST is added as a cost for for all 'accepted rows'. Increasing this variable will cause the optimizer to choose plans with less estimated rows. OPTIMIZER_DISK_READ_COST. This should be the time to do a random read of a 4096 byte block. OPTIMIZER_DISK_READ_COST * OPTIMIZER_DISK_READ_RATIO. Increasing OPTIMIZER_DISK_READ_RATIO will inform the optimizer that not all data is cached. OPTIMIZER_SCAN_SETUP_COST will increase the cost of a table scan. One can increase this to avoid using table scans. The costs for an engine are set the following way when the engine plugin is loaded/initialized:
points to the engine specific cost data. handlerton->update_optimizer_costs() to let the storage engine update the costs. handlerton->costs to TABLE_SHARE.optimizer_costs . handler::update_optimizer_costs() is called to allow the engine to tune the cost for this specific table instance. This is done to avoid having to take any "cost" mutex while running queries. handlerton->costs. This is why FLUSH TABLES is needed to activate new engine costs. handler::set_optimizer_costs() is called for each query to copy OPTIMIZER_WHERE_COST and OPTIMIZER_SCAN_SETUP_COST to the engine cost structure.
© 2023 MariaDB
Licensed under the Creative Commons Attribution 3.0 Unported License and the GNU Free Documentation License.
https://mariadb.com/kb/en/the-optimizer-cost-model-from-mariadb-11-0/