SlideShare a Scribd company logo
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization With MySQL
8.0 and MariaDB 10.3:
The Basics
Jaime Crespo
Percona Live Europe 2018
-Frankfurt, 5 Nov 2018-
dbahire.com/pleu18
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
2
Agenda
1. Introduction 6. Joins
2. Query Profiling 7. Subqueries
3. Access Types and Basic
Indexing Techniques
8. FULLTEXT, Geodata Search
and JSON support
4. Multi-Column Indexing 9. Conclusions
5. Break
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
3
INTRODUCTION
Query Optimization with MySQL 8.0 and MariaDB
10.3: The Basics
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
4
This is me fighting bad query
performance
●
Sr. Database Administrator
at Wikimedia Foundation
●
Used to work as a trainer
for Oracle (MySQL), as a
Consultant (Percona) and
as a Freelance
administrator
(DBAHire.com)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
5
Example Application (I)
• Wiktionary (and all Wikimedia projects’ data) is
licensed under the Creative Commons BY-SA-3.0
License and is Copyright its Contributors
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
6
Example Application (II)
• OSM Database is licensed under the Open
DataBase License and is Copyright OpenStreetMap
Contributors
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
7
Install the example databases
●
Downloads and instructions at:
http://dbahire.com/pleu18
– Requirements: a MySQL 8.0 or MariaDB 10.3
installation
– The Wiktionary and OSM extracts
●
Import them by doing:
$ tar xf <file> && cd <dir> && myloader -d .
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
8
Alternative: Query the Live
Database
●
https://quarry.wmflabs.org/
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
9
QUERY PROFILING
Query Optimization with MySQL 8.0 and MariaDB
10.3: The Basics
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
10
Which Queries Should I Optimize
First?
●
Tools:
– Application-level profiling
– General monitoring metrics (CPU, IO, etc.)
– Aggregated PROCESSLIST
– pt-query-digest
– PERFORMANCE_SCHEMA
●
They are not exclusive to each other
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
11
Application-Level profiling
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
12
General Monitoring Metrics
New index added
at 15:30h
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
13
pt-query-digest
• It is a 3rd party tool written in Perl, originally
created by Baron Schwartz
• It requires activation of the slow log:
– SET GLOBAL slow_query_log = 1;
– SET long_query_time = 0;
• In Percona Server and MariaDB it can provide extra
information:
– SHOW GLOBAL VARIABLES like 'log_slow_verbosity';
Be careful
with extra
IO and
latency!
Be careful
with extra
IO and
latency!
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
14
pt-query-digest Execution (I)
# 1094.7s user time, 9.4s system time, 141.22M rss, 205.21M vsz
# Current date: Wed Jul 1 07:32:28 2015
# Hostname: db1018
# Files: STDIN
# Overall: 4.66M total, 640 unique, 53.47 QPS, 0.02x concurrency _________
# Time range: 2015-06-30 07:00:10 to 2015-07-01 07:11:37
# Attribute total min max avg 95% stddev median
# ============ ======= ======= ======= ======= ======= ======= =======
# Exec time 1320s 1us 3s 283us 332us 3ms 152us
# Lock time 238s 0 13ms 51us 93us 39us 52us
# Rows sent 5.02M 0 4.16k 1.13 1.96 8.69 0.99
# Rows examine 9.50M 0 232.93k 2.14 3.89 261.15 0.99
# Merge passes 0 0 0 0 0 0 0
# Query size 1.06G 17 67.89k 243.89 511.45 368.99 192.76
# Boolean:
# Filesort 8% yes, 91% no
# Full scan 94% yes, 5% no
# Priority que 3% yes, 96% no
# Tmp table 29% yes, 70% no
# Tmp table on 1% yes, 98% no
Actual execution
on Wikipedia
production
servers
Actual execution
on Wikipedia
production
servers
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
15
pt-query-digest Execution (II)
# Profile
# Rank Query ID Response time Calls R/Call V/M Item
# ==== ================== ============== ======= ====== ===== ============
# 1 0xSANITIZED 242.2765 18.4% 691005 0.0004 0.00 SELECT revision page user
# 2 0xSANITIZED 204.7052 15.5% 80863 0.0025 0.01 SELECT revision page user
# 3 0xSANITIZED 162.8476 12.3% 1025179 0.0002 0.00 SELECT page
# 4 0xSANITIZED 68.1164 5.2% 93928 0.0007 0.01 SELECT revision page user
# 5 0xSANITIZED 66.8302 5.1% 354562 0.0002 0.00 SELECT page revision
# 6 0xSANITIZED 57.0374 4.3% 211631 0.0003 0.00 SELECT page revision
# 7 0xSANITIZED 44.0751 3.3% 6925 0.0064 0.07 SELECT page categorylinks
category
# 8 0xSANITIZED 35.0655 2.7% 9689 0.0036 0.00 SELECT text
# 9 0xSANITIZED 29.4363 2.2% 152259 0.0002 0.00 SELECT page
# 10 0xSANITIZED 24.1864 1.8% 176927 0.0001 0.00 SELECT msg_resource
# 11 0xSANITIZED 23.7016 1.8% 144807 0.0002 0.00 SELECT page_restrictions
# 12 0xSANITIZED 16.6547 1.3% 10135 0.0016 0.03 SELECT revision
# 13 0xSANITIZED 15.0564 1.1% 263809 0.0001 0.00 SET
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
16
pt-query-digest Execution (III)
# Query 1: 7.93 QPS, 0.00x concurrency, ID 0xSANITIZED at byte 1553864032
# This item is included in the report because it matches –limit.
# Scores: V/M = 0.00
# Time range: 2015-06-30 07:00:10 to 2015-07-01 07:11:37
# Attribute pct total min max avg 95% stddev median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count 14 691005
# Exec time 18 242s 163us 91ms 350us 348us 563us 301us
# Lock time 26 63s 47us 7ms 91us 103us 14us 84us
# Rows sent 12 657.18k 0 1 0.97 0.99 0.16 0.99
# Rows examine 6 657.18k 0 1 0.97 0.99 0.16 0.99
# Query size 31 345.42M 501 749 524.16 537.02 9.22 511.45
# String:
# Databases itwiki (225976/32%), enwiktiona... (219461/31%)... 15 more
# Hosts
# Users wikiuser
# Query_time distribution
# 1us
# 10us
# 100us ################################################################
# 1ms #
# 10ms #
# 100ms
# 1s
# 10s+
# Tables
# SHOW TABLE STATUS FROM `enwiktionary` LIKE 'revision'G
# SHOW CREATE TABLE `enwiktionary`.`revision`G
# SHOW TABLE STATUS FROM `enwiktionary` LIKE 'page'G
# SHOW CREATE TABLE `enwiktionary`.`page`G
# SHOW TABLE STATUS FROM `enwiktionary` LIKE 'user'G
# SHOW CREATE TABLE `enwiktionary`.`user`G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT /* Revision::fetchFromConds SANITIZED */ * FROM `revision`
INNER JOIN `page` ON ((page_id = rev_page)) LEFT JOIN `user` ON
((rev_user != 0) AND (user_id = rev_user)) WHERE page_namespace = '0' AND
page_title = 'SANITIZED' AND (rev_id=page_latest) LIMIT 1G
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
17
PERFORMANCE_SCHEMA
• Monitoring schema (engine) enabled by
default since MySQL 5.6
– performance_schema = 1 (it is not dynamic)
• Deprecates the old query profiling
• It is way more user-friendly when combined
with the SYS schema/ps_helper (a set of
views and stored procedures created by Mark
Leith)
– Included by default since 5.7.7
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
18
Installation of the SYS Schema
<5.7/MariaDB
$ git clone https://github.com/MarkLeith/mysql-sys.git
Cloning into 'mysql-sys'...
remote: Counting objects: 926, done.
remote: Compressing objects: 100% (73/73), done.
remote: Total 926 (delta 35), reused 6 (delta 2)
Receiving objects: 100% (926/926), 452.19 KiB | 225.00
KiB/s, done.
Resolving deltas: 100% (584/584), done.
$ cd mysql-sys/
$ mysql < sys_56.sql
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
19
Example Usage: Discovering Unused
Indexes
mysql (osm) > SELECT * FROM sys.schema_unused_indexes LIMIT 5;
+---------------+-------------------+---------------------------------+
| object_schema | object_name | index_name |
+---------------+-------------------+---------------------------------+
| osm | acls | acls_k_idx |
| osm | changeset_tags | changeset_tags_id_idx |
| osm | current_nodes | current_nodes_timestamp_idx |
| osm | current_nodes | current_nodes_tile_idx |
| osm | current_relations | current_relations_timestamp_idx |
+---------------+-------------------+---------------------------------+
5 rows in set (0.04 sec)
mysql-5.7.8 (osm) > SELECT * FROM current_nodes WHERE tile = 100;
...
mysql-5.7.8 (osm) > SELECT * FROM sys.schema_unused_indexes LIMIT 5;
+---------------+-------------------+---------------------------------+
| object_schema | object_name | index_name |
+---------------+-------------------+---------------------------------+
| osm | acls | acls_k_idx |
| osm | changeset_tags | changeset_tags_id_idx |
| osm | current_nodes | current_nodes_timestamp_idx |
| osm | current_relations | current_relations_timestamp_idx |
| osm | current_relations | changeset_id |
+---------------+-------------------+---------------------------------+
5 rows in set (0.03 sec)
With enough
activity, it can
help us clean
up our schema
With enough
activity, it can
help us clean
up our schema
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
20
Example Usage: Slow Queries
(ordered by server time)
*************** 1. row ***************
query: SELECT `way_id` AS
`id` , `v` FROM `way_tags` WHERE `v` LIKE ?
db: osm
full_scan: *
exec_count: 15
err_count: 0
warn_count: 0
total_latency: 7.83 s
max_latency: 1.33 s
avg_latency: 521.84 ms
lock_latency: 17.94 ms
rows_sent: 6779
rows_sent_avg: 452
mysql (osm) > SELECT * FROM sys.statement_analysis LIMIT 10G
rows_examined: 20152155
rows_examined_avg: 1343477
rows_affected: 0
rows_affected_avg: 0
tmp_tables: 0
tmp_disk_tables: 0
rows_sorted: 0
sort_merge_passes: 0
digest:
21f90695b1ebf20a5f4d4c1e5e860f58
first_seen: 2014-11-01 17:04:51
last_seen: 2014-11-01 17:05:22
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
21
Example Usage: Top Queries
Creating Temporary Tables
mysql (osm) > SELECT * FROM sys.statements_with_temp_tables
WHERE db = 'osm' LIMIT 10G
*************************** 1. row ***************************
query: SELECT ? AS TYPE , `node_id` A ... gs` WHERE `k` = ? AND `v` = ?
db: osm
exec_count: 11
total_latency: 7.57 s
memory_tmp_tables: 11
disk_tmp_tables: 0
avg_tmp_tables_per_query: 1
tmp_tables_to_disk_pct: 0
first_seen: 2014-11-01 17:33:55
last_seen: 2014-11-01 17:34:45
digest: 5e6e82799b7c7c0e5c57cfe63eb98d5d
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
22
Example Usage: Top Queries
Creating Temporary Tables (cont.)
mysql (osm) > SELECT DIGEST_TEXT FROM performance_schema.events_statements_summary_by_digest WHERE
digest = '5e6e82799b7c7c0e5c57cfe63eb98d5d'G
*************************** 1. row ***************************
DIGEST_TEXT: SELECT ? AS TYPE , `node_id` AS `id` FROM `node_tags` WHERE `k` = ? AND `v` = ? UNION
SELECT ? AS TYPE , `way_id` AS `id` FROM `way_tags` WHERE `k` = ? AND `v` = ? UNION SELECT ? AS
TYPE , `relation_id` AS `id` FROM `relation_tags` WHERE `k` = ? AND `v` = ?
1 row in set (0.00 sec)
mysql-5.7.8 (osm) > EXPLAIN SELECT 'node' as type, node_id as id FROM node_tags WHERE k='amenity'
and v='cafe' UNION SELECT 'way' as type, way_id as id FROM way_tags WHERE k='amenity' and v='cafe'
UNION SELECT 'relation' as type, relation_id as id FROM relation_tags WHERE k='amenity' and
v='cafe';
+----+--------------+---------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+---------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+
| 1 | PRIMARY | node_tags | NULL | ALL | NULL | NULL | NULL | NULL | 851339 | 0.00 | Using where |
| 2 | UNION | way_tags | NULL | ALL | NULL | NULL | NULL | NULL | 1331016 | 0.00 | Using where |
| 3 | UNION | relation_tags | NULL | ALL | NULL | NULL | NULL | NULL | 63201 | 0.00 | Using where |
| NULL | UNION RESULT | <union1,2,3> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+---------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+
4 rows in set, 1 warning (0.01 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
23
ACCESS TYPES AND BASIC
INDEXING TECHNIQUES
Query Optimization with MySQL 8.0 and MariaDB
10.3: The Basics
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
24
EXPLAIN
●
Essential to understand the execution plan of our
queries
– Works on SELECTs, INSERTs, UPDATEs,
REPLACEs, DELETEs and connections
– Fully documented on:
https://dev.mysql.com/doc/refman/8.0/en/expl
ain-output.html
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
25
EXPLAIN Example
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page
WHERE page_title = 'German';
+------+-------------+-------+------+---------------
+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys |
key | key_len | ref | rows | Extra |
+------+-------------+-------+------+---------------
+------+---------+------+--------+-------------+
| 1 | SIMPLE | page | ALL | NULL |
NULL | NULL | NULL | 778885 | Using where |
+------+-------------+-------+------+---------------
+------+---------+------+--------+-------------+
1 row in set (0.00 sec)
Difficult to see
something
Difficult to see
something
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
26
EXPLAIN Example (vertical
format)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title = 'German'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 778885
Extra: Using where
1 row in set (0.00 sec)
Use G for
vertical
formatting
Use G for
vertical
formatting
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
27
EXPLAIN Example (id)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title = 'German'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 778885
Extra: Using where
1 row in set (0.00 sec)
Indicates nesting level,
not execution order
Indicates nesting level,
not execution order
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
28
EXPLAIN Example (select_type)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title = 'German'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 778885
Extra: Using where
1 row in set (0.00 sec)
Not a subquery or a
UNION
Not a subquery or a
UNION
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
29
EXPLAIN Example (table)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title = 'German'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 778885
Extra: Using where
1 row in set (0.00 sec)
Table scanned for
this step
Table scanned for
this step
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
30
EXPLAIN Example (type)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title = 'German'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 778885
Extra: Using where
1 row in set (0.00 sec)
All rows are read for
this table (FULL
TABLE SCAN)
All rows are read for
this table (FULL
TABLE SCAN)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
31
EXPLAIN Example (rows)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title = 'German'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 778885
Extra: Using where
1 row in set (0.00 sec)
Estimated number
of rows to be read
(all table rows)
Estimated number
of rows to be read
(all table rows)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
32
How to improve performance?
●
Let's add an index on page.page_title:
MariaDB [dewiktionary]> ALTER TABLE page ADD INDEX
page_title (page_title);
Query OK, 0 rows affected (22.70 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> SELECT * FROM page
WHERE page_title = 'German';
1 row in set (0.10 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
33
Index creation results (type)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title = 'German'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ref
possible_keys: page_title
key: page_title
key_len: 257
ref: const
rows: 1
Extra: Using index condition
1 row in set (0.00 sec)
type: ref means that an
equality comparison will
be checked against an
index and several results
could be returned
type: ref means that an
equality comparison will
be checked against an
index and several results
could be returned
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
34
Index creation results
(possible_keys and key)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title = 'German'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ref
possible_keys: page_title
key: page_title
key_len: 257
ref: const
rows: 1
Extra: Using index condition
1 row in set (0.00 sec)
index(es) that the
optimizer considered
potentially useful, and
final index chosen
index(es) that the
optimizer considered
potentially useful, and
final index chosen
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
35
Index creation results (ref)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title = 'German'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ref
possible_keys: page_title
key: page_title
key_len: 257
ref: const
rows: 1
Extra: Using index condition
1 row in set (0.00 sec)
Index is compared
with a constant, not
with another table
Index is compared
with a constant, not
with another table
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
36
Index creation results (rows)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title = 'German'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ref
possible_keys: page_title
key: page_title
key_len: 257
ref: const
rows: 1
Extra: Using index condition
1 row in set (0.00 sec)
Only 1 row read. In this
case, estimation is exact
(thanks to index dive)
Only 1 row read. In this
case, estimation is exact
(thanks to index dive)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
37
Index creation results (query
time)
MariaDB [dewiktionary]> SELECT * FROM page
WHERE page_title = 'German';
1 row in set (0.00 sec) Query time has been
reduced substantially
Query time has been
reduced substantially
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
38
Types of indexes
●
BTREE
– B-TREE in MyISAM, B+TREE in InnoDB
●
HASH
– Only available for MEMORY and NDB
●
FULLTEXT
– Inverted indexes in MyISAM and InnoDB
●
SPATIAL
– RTREEs in MyISAM and InnoDB
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
39
Finding “German” with a BTREE
GoalGoal
NastyNasty
BossBoss
EtchEtch
LeekLeek
SchoolSchool
AvastAvast
GolfGolf
LandscapeLandscape
RoverRover
GermanGerman
HarlemHarlem
MaelstromMaelstrom
WalrusWalrus
TABLETABLE
IcebergIceberg
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
40
Finding “German” with a BTREE
AvastAvast
GolfGolf
LandscapeLandscape
RoverRover
GermanGerman
HarlemHarlem
MaelstromMaelstrom
WalrusWalrus
TABLETABLE
GoalGoal
NastyNasty
BossBoss
EtchEtch
LeekLeek
SchoolSchool
IcebergIceberg
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
41
Do indexes always work? (1/2)
●
Can we use an index to make this query
faster?
SELECT * FROM page WHERE page_title like 'Spa%';
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
42
It is a range
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title like 'Spa%'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: range
possible_keys: page_title
key: page_title
key_len: 257
ref: NULL
rows: 563
Extra: Using index condition
1 row in set (0.00 sec)
Despite not being an
equality, we can use the
index to find the values
quickly
Despite not being an
equality, we can use the
index to find the values
quickly
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
43
EtchEtch
BTREE Indexes can be used for
ranges, too
AvastAvast
GolfGolf
LandscapeLandscape
RoverRover
GermanGerman
HarlemHarlem
MaelstromMaelstrom
WalrusWalrus
TABLETABLE
GoalGoal
NastyNasty
BossBoss
LeekLeek
SchoolSchool
IcebergIceberg
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
44
Do indexes always work? (2/2)
●
What about this other query?
SELECT * FROM page WHERE page_title like '%erman';
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
45
Let's check with EXPLAIN
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title like '%erman'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 778885
Extra: Using where
1 row in set (0.00 sec)
No index can be used
for filtering. A full
table scan is
performed.
No index can be used
for filtering. A full
table scan is
performed.
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
46
BTREE Index
AvastAvast
GolfGolf
LandscapeLandscape
RoverRover
GermanGerman
HarlemHarlem
MaelstromMaelstrom
WalrusWalrus
TABLETABLE
?
GoalGoal
NastyNasty
BossBoss
EtchEtch
LeekLeek
SchoolSchool
IcebergIceberg
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
47
Btree indexes usage (I)
●
Filtering (“WHERE”)
– Equality (operator '=')
– Ranges (BETWEEN … AND, >, <, >=, <=, like
'prefix%')
– “EXISTS” operators: IN, OR on the same column
●
Ordering
– ORDER BY (indexed columns)
– GROUP BY (indexed columns)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
48
Btree indexes usage (II)
●
Returning values directly from the index
– Covering index
– Functions like max(), min(), etc.
●
Reduced contention (writes)
– Locking less rows, increasing concurrency
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
49
type: const
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE
rev_id = 2G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: const
rows: 1
Extra:
1 row in set (0.00 sec)
'const' is a special case of 'ref',
when the index can assure
that only 1 result can be
returned (equality + primary
key or unique key). It is faster.
'const' is a special case of 'ref',
when the index can assure
that only 1 result can be
returned (equality + primary
key or unique key). It is faster.
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
50
type: NULL
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE
rev_id = -1G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: NULL
type: NULL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
Extra: Impossible WHERE noticed after reading const
tables
1 row in set (0.00 sec)
'NULL' is not really a plan,
just an optimization that
allows immediately
discarding impossible
conditions
'NULL' is not really a plan,
just an optimization that
allows immediately
discarding impossible
conditions
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
51
type: ref_or_null
MariaDB [osm]> EXPLAIN SELECT * FROM nodes WHERE tile = 1 or
tile is nullG
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: nodes
type: ref_or_null
possible_keys: nodes_tile_idx
key: nodes_tile_idx
key_len: 5
ref: const
rows: 2
Extra: Using index condition; Using where
1 row in set (0.00 sec)
Equivalent to 'ref', but
also takes into account
NULL values
Equivalent to 'ref', but
also takes into account
NULL values
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
52
type: range (using IN / OR)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_title IN ('German', 'English', 'Spanish')G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: range
possible_keys: page_title
key: page_title
key_len: 257
ref: NULL
rows: 4
Extra: Using index condition
1 row in set (0.00 sec)
Despite being a range, its
execution is very
different from ranges
using like, between or
inequality operators
Despite being a range, its
execution is very
different from ranges
using like, between or
inequality operators
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
53
Is this a bug? (1/2)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_namespace > 200G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ref
possible_keys: name_title
key: name_title
key_len: 4
ref: const
rows: 50
Extra:
1 row in set (0.00 sec)
An index is used to
return pages with
namespace>200
An index is used to
return pages with
namespace>200
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
54
Is this a bug? (2/2)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_namespace < 200G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ALL
possible_keys: name_title
key: NULL
key_len: NULL
ref: NULL
rows: 778885
Extra: Using where
1 row in set (0.00 sec)
The index is not used
with ns=0
The index is not used
with ns=0
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
55
Using an index is sometimes
suboptimal
This point is not
fixed, and
depends on the
hardware and
buffer state
This point is not
fixed, and
depends on the
hardware and
buffer state
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
56
What index should we add to
make this query faster?
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE
left(rev_timestamp, 6) = '201809'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1376651
Extra: Using where
1 row in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
57
The table has already an index on
rev_timestamp
MariaDB [dewiktionary]> SHOW CREATE TABLE revisionG
*************************** 1. row ***************************
Table: revision
Create Table: CREATE TABLE `revision` (
...
KEY `rev_timestamp` (`rev_timestamp`),
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
58
We need to rewrite the query
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE
rev_timestamp >= '201509' and rev_timestamp < '201510'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys: rev_timestamp
key: rev_timestamp
key_len: 14
ref: NULL
rows: 24588
Extra: Using index condition
1 row in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
59
This transformation is not trivial
or even possible in all cases
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE
substr(rev_timestamp, 5, 2) = '09'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
Rows: 1376651
Extra: Using where
1 row in set (0.00 sec)
Can you think of a way
to improve this query?
Can you think of a way
to improve this query?
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
60
Indexes for Ordering
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page ORDER BY
page_touched DESC LIMIT 10G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 778885
Extra: Using filesort
1 row in set (0.00 sec)
"Using filesort" indicates that
an ordering is needed before
returning the results
"Using filesort" indicates that
an ordering is needed before
returning the results
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
61
If that is frequent, we can create
an index on page_touched...[dewiktionary]> ALTER TABLE page ADD INDEX page_page_touched(page_touched);
Query OK, 0 rows affected (3.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
[dewiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_touched DESC LIMIT
10G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: index
possible_keys: NULL
key: page_page_touched
key_len: 16
ref: NULL
rows: 10
Extra:
1 row in set (0.00 sec)
The index does not produce any
advantage for filtering
The index does not produce any
advantage for filtering
However, it is very effective by
helping to avoid the sort phase
However, it is very effective by
helping to avoid the sort phase
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
62
It can return data in index order
faster
AvastAvast
GolfGolf
LandscapeLandscape
RoverRover
GermanGerman
HarlemHarlem
MaelstromMaelstrom
WalrusWalrus
TABLETABLE
GoalGoal
NastyNasty
BossBoss
EtchEtch
LeekLeek
SchoolSchool
IcebergIceberg
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
63
Trick: ORDER BY NULL avoids
filesortMariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision GROUP BY substr(rev_timestamp, 5,
2) = '09'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1376651
Extra: Using temporary; Using filesort
1 row in set (0.00 sec)
MariaDB [nlwiktionary]> EXPLAIN SELECT * FROM revision GROUP BY substr(rev_timestamp, 5,
2) = '09' ORDER BY NULLG
*************************** 1. row ***************************
...
rows: 1376651
Extra: Using temporary
1 row in set (0.00 sec)
The advantage is not too big, but
it avoids the filesort
The advantage is not too big, but
it avoids the filesort
There is no good index in this
case
There is no good index in this
case
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
64
Does LIMIT improve the
performance? (I)
MariaDB [dewiktionary]> ALTER TABLE page ADD INDEX page_touched (page_touched); […]
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_touchedG
*************************** 1. row ***************************
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 783231
Extra: Using filesort
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_touched LIMIT 10G
*************************** 1. row ***************************
type: index
possible_keys: NULL
key: page_page_touched
key_len: 16
ref: NULL
rows: 10
Extra:
1 row in set (0.00 sec)
In some cases it can be
essential to allow
effective usage of the
indexes
In some cases it can be
essential to allow
effective usage of the
indexes
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
65
Does LIMIT improve the
performance? (II)MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision ORDER BY rev_commentG
*************************** 1. row ***************************
[...] type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 817636
Extra: Using filesort
1 row in set (0.00 sec)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision ORDER BY rev_comment LIMIT 10G
*************************** 1. row ***************************
[…] table: revision
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 817636
Extra: Using filesort
1 row in set (0.00 sec)
In other cases, it has no
effect on the scanned
rows (just on the
returned ones)
In other cases, it has no
effect on the scanned
rows (just on the
returned ones)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
66
Does LIMIT improve the
performance? (III)MariaDB [nlwiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_title LIMIT 100G
*************************** 1. row ***************************
type: index
possible_keys: NULL
key: page_title
key_len: 257
ref: NULL
rows: 100
Extra:
1 row in set (0.00 sec)
MariaDB [nlwiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_title LIMIT 10000, 100G
*************************** 1. row ***************************
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 90956
Extra: Using filesort
1 row in set (0.00 sec)
In this case,
performance will vary
depending on the
offset (not ideal)
In this case,
performance will vary
depending on the
offset (not ideal)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
67
Indexes and GROUP BY (no
indexes)
MariaDB [dewiktionary]> EXPLAIN SELECT rev_page, count(*) FROM
revision IGNORE INDEX(rev_page_id, page_timestamp,
page_user_timestamp) GROUP BY rev_pageG
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1376651
Extra: Using temporary; Using filesort
1 row in set (0.00 sec)
Without indexes, a temporary
table is created to order results
Without indexes, a temporary
table is created to order results
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
68
Indexes and GROUP BY
(rev_page_id)
MariaDB [dewiktionary]> EXPLAIN SELECT rev_page, count(*) FROM
revision GROUP BY rev_pageG
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: index
possible_keys: NULL
key: rev_page_id
key_len: 8
ref: NULL
rows: 1376651
Extra: Using index
1 row in set (0.00 sec)
The index does not produce any
advantage for filtering (there is
no WHERE clause)
The index does not produce any
advantage for filtering (there is
no WHERE clause)
However, thanks to it we avoid a
sort and a temporary table
However, thanks to it we avoid a
sort and a temporary table
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
69
type: index, loose index scan and
covering index (1/3)
node_id version k v
234234344545 1 leaves deciduous
234234344546 3 name Northolt Road
234234344548 5 gps:latitude 490018321N
234234344549 6 access uncontrolled
234234344550 1 editor JOSM
234234344551 9 name Big Ben
234234344552 1 source survey
234234344557 1 name London Plane
CarlsbergCarlsberg
recyclingrecycling
Accurate as
of 2010
Accurate as
of 2010
Gwynne
Place
Gwynne
Place
Manor PlaceManor Place
treetree
490018321N490018321N
deciduousdeciduous
London PlaneLondon Plane
surveysurvey
Big BenBig Ben
JOSMJOSM
Northolt RoadNortholt Road
uncontrolleduncontrolled
London
Borough of
Southwark
London
Borough of
Southwark
With 'type:index', all rows are
read in index order (full index
scan)
With 'type:index', all rows are
read in index order (full index
scan)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
70
type: index, loose index scan and
covering index (2/3)
node_id version k v
234234344545 1 leaves deciduous
234234344546 3 name Northolt Road
234234344548 5 gps:latitude 490018321N
234234344549 6 access uncontrolled
234234344550 1 editor JOSM
234234344551 9 name Big Ben
234234344552 1 source survey
234234344557 1 name London Plane
CarlsbergCarlsberg
recyclingrecycling
Accurate as
of 2010
Accurate as
of 2010
Gwynne
Place
Gwynne
Place
Manor PlaceManor Place
treetree
490018321N490018321N
deciduousdeciduous
London PlaneLondon Plane
surveysurvey
Big BenBig Ben
JOSMJOSM
Northolt RoadNortholt Road
uncontrolleduncontrolled
London
Borough of
Southwark
London
Borough of
Southwark
If we have in addition 'Using
index for group-by' we have the
loose index scan optimization
If we have in addition 'Using
index for group-by' we have the
loose index scan optimization
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
71
Loose Index Scan Example
MariaDB [dewiktionary]> EXPLAIN SELECT rev_page,
max(rev_timestamp) FROM revision GROUP BY rev_pageG
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys: NULL
key: page_timestamp
key_len: 4
ref: NULL
rows: 80980
Extra: Using index for group-by
1 row in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
72
type: index, loose index scan and
covering index (3/3)
CarlsbergCarlsberg
recyclingrecycling
Accurate as
of 2010
Accurate as
of 2010
Gwynne
Place
Gwynne
Place
Manor PlaceManor Place
treetree
490018321N490018321N
deciduousdeciduous
London PlaneLondon Plane
surveysurvey
Big BenBig Ben
JOSMJOSM
Northolt RoadNortholt Road
uncontrolleduncontrolled
London
Borough of
Southwark
London
Borough of
Southwark
If we have in addition 'Using
index' we have the covering index
optimization
If we have in addition 'Using
index' we have the covering index
optimization
node_id version k v
234234344545 1 leaves deciduous
234234344546 3 name Northolt Road
234234344548 5 gps:latitude 490018321N
234234344549 6 access uncontrolled
234234344550 1 editor JOSM
234234344551 9 name Big Ben
234234344552 1 source survey
234234344557 1 name London Plane
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
73
Covering Index Example (1/3)
MariaDB [dewiktionary]> ALTER TABLE revision DROP INDEX rev_page_id, drop index page_timestamp,
drop index page_user_timestamp;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> EXPLAIN SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page =
31579G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1376651
Extra: Using where
1 row in set (0.00 sec)
MariaDB [dewiktionary]> SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page = 31579G
*************************** 1. row ***************************
count(DISTINCT rev_user): 1
1 row in set (3.57 sec)
Let's start with no indexesLet's start with no indexes
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
74
Covering Index Example (2/3)
MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX revision_rev_page(rev_page);
Query OK, 0 rows affected (12.38 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> EXPLAIN SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page =
31579G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ref
possible_keys: revision_rev_page
key: revision_rev_page
key_len: 4
ref: const
rows: 4038
Extra:
1 row in set (0.00 sec)
MariaDB [dewiktionary]> SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page = 31579G
*************************** 1. row ***************************
count(DISTINCT rev_user): 1
1 row in set (0.04 sec)
Adding an index on rev_page
increases the speed due to
improved filtering
Adding an index on rev_page
increases the speed due to
improved filtering
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
75
Covering Index Example (3/3)
MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX revision_rev_page_rev_user(rev_page,
rev_user);
Query OK, 0 rows affected (13.20 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> EXPLAIN SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page = 31579
G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ref
possible_keys: revision_rev_page,revision_rev_page_rev_user
key: revision_rev_page_rev_user
key_len: 4
ref: const
rows: 4038
Extra: Using index
1 row in set (0.00 sec)
MariaDB [dewiktionary]> SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page = 31579G
*************************** 1. row ***************************
count(DISTINCT rev_user): 1
1 row in set (0.00 sec)
rev_page, rev_user does not
increase the index
selectiveness, but allows
returning results directly
from the index
rev_page, rev_user does not
increase the index
selectiveness, but allows
returning results directly
from the index
The speed difference can be hugeThe speed difference can be huge
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
76
MULTI-COLUMN INDEXES
Query Optimization with MySQL 8.0 and MariaDB
10.3: The Basics
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
77
In many cases, conditions are
applied on more than one column
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE
rev_page = 31579 and rev_timestamp < '2008'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1376651
Extra: Using where
1 row in set (0.00 sec)
Assuming there were no previously
created indexes, which would the
optimal one be?
Assuming there were no previously
created indexes, which would the
optimal one be?
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
78
Options for indexes
●
1 index on column (rev_page)
●
1 index on column (rev_timestamp)
●
2 indexes, 1 on (rev_page) and another on
(rev_timestamp)
●
1 multi-column index on (rev_page, rev_timestamp)
●
1 multi-column index on (rev_timestamp,rev_page)
Are these last 2 different from each other?
Would it depend on the query order?
Are these last 2 different from each other?
Would it depend on the query order?
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
79
A brief reminder about query parsing
SELECT * FROM revision WHERE rev_page = 31579 and rev_timestamp < '2008'
SELECTSELECT
FieldsFields FROMFROM
revision.
rev_page
revision.
rev_page
==
(int constant)
31579
(int constant)
31579
revision.
rev_timest
amp
revision.
rev_timest
amp
<<
(string
constant)
'2008'
(string
constant)
'2008'
WHEREWHERE
revision.
rev_id
revision.
rev_id
... revisionrevision
Order in the query is not
important
Order in the query is not
important
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
80
Index on (rev_page)
MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX rev_page (rev_page);
Query OK, 0 rows affected (11.87 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page = 31579 and
rev_timestamp < '2008'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ref
possible_keys: rev_page
key: rev_page
key_len: 4
ref: const
rows: 4038
Extra: Using where
1 row in set (0.00 sec)
Query time improves
significantly with this
index
Query time improves
significantly with this
index
Fewer rows are
scanned
Fewer rows are
scanned
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
81
Adding (rev_timestamp)
MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX rev_timestamp
(rev_timestamp);
Query OK, 0 rows affected (17.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page =
31579 and rev_timestamp < '2008'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ref
possible_keys: rev_page,rev_timestamp
key: rev_page
key_len: 4
ref: const
rows: 4038
Extra: Using where
1 row in set (0.01 sec)
rev_page is preferred
over rev_timestamp
rev_page is preferred
over rev_timestamp
In general, only one index can
be used per table access
In general, only one index can
be used per table access
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
82
Forcing the use of
(rev_timestamp)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision FORCE
INDEX(rev_timestamp) WHERE rev_page = 31579 and rev_timestamp
< '2008'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys: rev_timestamp
key: rev_timestamp
key_len: 14
ref: NULL
rows: 688325
Extra: Using index condition; Using where
1 row in set (0.00 sec)
It is a range
access
It is a range
access
A lot more accessed
rows
A lot more accessed
rows
Forcing the index is worse than type:ALL!Forcing the index is worse than type:ALL!
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
83
Adding (rev_page,
rev_timestamp)MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX
rev_page_rev_timestamp(rev_page, rev_timestamp);
Query OK, 0 rows affected (14.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page =
31579 and rev_timestamp < '2008'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys: rev_page,rev_timestamp,rev_page_rev_timestamp
key: rev_page_rev_timestamp
key_len: 18
ref: NULL
rows: 530
Extra: Using index condition
1 row in set (0.00 sec)
Reduced number of
rows scanned
Reduced number of
rows scanned
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
84
Is (rev_timestamp, rev_page) a
better option?MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX rev_timestamp_rev_page
(rev_timestamp, rev_page);
Query OK, 0 rows affected (16.80 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page = 31579 and
rev_timestamp < '2008'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys: rev_page,rev_timestamp,rev_page_rev_timestamp,rev_timestamp_rev_page
key: rev_page_rev_timestamp
key_len: 18
ref: NULL
rows: 530
Extra: Using index condition
1 row in set (0.00 sec)
Previous index is still
preferred, why?
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
85
Forcing (rev_timestamp,
rev_page)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision FORCE
INDEX(rev_timestamp_rev_page) WHERE rev_page = 31579 and
rev_timestamp < '2008'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys: rev_timestamp_rev_page
key: rev_timestamp_rev_page
key_len: 18
ref: NULL
rows: 688325
Extra: Using index condition
1 row in set (0.00 sec)
Only the 1st column* is being
used effectively for filtering
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
86
A compound index produces a single
tree ordered by the 2 values
(12301,
'2018')
(12301,
'2018')
(31579,
'2005')
(31579,
'2005')
(8005,
'2016')
(8005,
'2016')
(15444,
'2011')
(15444,
'2011')
(31579,
'2003')
(31579,
'2003')
(31579,
'2017')
(31579,
'2017')
(4611, '2015')(4611, '2015')
(12702, '2014')(12702, '2014')
(30024, '2003')(30024, '2003')
(31579, '2008')(31579, '2008')
(11771, '2013')(11771, '2013')
(23305, '2006')(23305, '2006')
(31579, '2004')(31579, '2004')
(428105, '2015')(428105, '2015')
TABLETABLE
(23443,
'2016')
(23443,
'2016')
rev_page = 31579 and
rev_timestamp > '2008'
(rev_page,
rev_timestamp)
(rev_page,
rev_timestamp)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
87
The alternative index cannot be used
in such an effective way
('2008', 800)('2008', 800)
('2016', 24)('2016', 24)
('2004', 107)('2004', 107)
('2008',
55765)
('2008',
55765)
('2014', 999)('2014', 999)
('2018',
31579)
('2018',
31579)
('2001', 355)('2001', 355)
('2008', 31579)('2008', 31579)
('2012', 791)('2012', 791)
('2018', 1784)('2018', 1784)
('2008', 206)('2008', 206)
('2009', 201)('2009', 201)
('2015', 31579)('2015', 31579)
('2018', 79405)('2018', 79405)
TABLETABLE
('2011',
13605)
('2011',
13605)
(rev_timestamp,
rev_page)
(rev_timestamp,
rev_page)
rev_page = 31579 and
rev_timestamp > '2008'
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
88
Order and column selection
• Range access using >, <, >=, <=, BETWEEN
can only be filtered once effectively, at the
end of an index
• When selecting indexes, prefer columns with
high cardinality (very selective)
– The optimal index can depend on the constants used
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
89
Can we filter and sort at the same
time using indexes?
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE
rev_comment='' ORDER BY rev_timestamp ASCG
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 817636
Extra: Using where; Using filesort
1 row in set (0.00 sec)
This query is slow
because a) the full
table scan
This query is slow
because a) the full
table scan
b) Required sort
after filtering
b) Required sort
after filtering
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
90
Adding an index on
(rev_comment, rev_timestamp)MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX
rev_comment_rev_timestamp (rev_comment, rev_timestamp);
Query OK, 0 rows affected (16.19 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_comment=''
ORDER BY rev_timestamp ASCG
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ref
possible_keys: rev_comment_rev_timestamp
key: rev_comment_rev_timestamp
key_len: 769
ref: const
rows: 266462
Extra: Using index condition; Using where
1 row in set (0.00 sec)
Both ‘type: ALL’
and ‘filesort’ have
disappeared
Both ‘type: ALL’
and ‘filesort’ have
disappeared
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
91
This is not always possible
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE
rev_len > 5500 ORDER BY rev_timestamp ASCG
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys: rev_len_rev_timestamp
key: rev_len_rev_timestamp
key_len: 5
ref: NULL
rows: 38744
Extra: Using index condition; Using filesort
1 row in set (0.00 sec)
The range makes impossible
to use the index optimally
for the ORDER BY: either we
filter (rev_len) or sort
(rev_timestamp)
The range makes impossible
to use the index optimally
for the ORDER BY: either we
filter (rev_len) or sort
(rev_timestamp)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
92
A Strange Game. The Only
Winning Move is Not to Play
mysql (osm) > SELECT * FROM nodes FORCE INDEX(version_idx)
WHERE version < 15 ORDER BY changeset_id;
/* type: range, Using filesort */
2859673 rows in set (30.58 sec)
mysql (osm) > SELECT * FROM nodes FORCE
INDEX(changeset_id_idx) WHERE version < 15 ORDER BY
changeset_id;
/* type: index */
2859673 rows in set (30.92 sec)
mysql (osm) > SELECT * FROM nodes WHERE version < 15 ORDER BY
changeset_id;
/* type: ALL, Using filesort */
2859673 rows in set (16.54 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
95
DESC Indexes (new in 8.0)
●
Regular indexes could also be used to scan
rows in inverse order
– It had a penalty
– It improves certain queries combining
DESC and ASC order
mysql-8.0 [dewiktionary]> ALTER TABLE page ADD INDEX
`page_redirect_namespace_len_DESC`
(`page_is_redirect`,`page_namespace`,`page_len` DESC);
Query OK, 0 rows affected, 1 warning (3.96 sec)
Records: 0 Duplicates: 0 Warnings: 1
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
96
Descending Indexes in Action
mysql-8.0 [dewiktionary]> EXPLAIN SELECT * FROM page WHERE
page_is_redirect = 1 ORDER BY page_namespace, page_len DESCG
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: page
type: ref
possible_keys:
page_redirect_namespace_len,page_redirect_namespace_len_DESC
key: page_redirect_namespace_len
key_len: 1
ref: const
rows: 2404
Extra: Using where
1 row in set (0.00 sec)
Filesort
avoided
Filesort
avoided
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
97
Index Condition Pushdown
MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX
rev_len_rev_timestamp(rev_len, rev_timestamp);
Query OK, 0 rows affected (18.11 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> SET optimizer_switch='index_condition_pushdown=on'; EXPLAIN
SELECT * FROM revision WHERE rev_timestamp < '2008' AND rev_len > 10000G
Query OK, 0 rows affected (0.00 sec)
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys: rev_timestamp,rev_timestamp_rev_page,rev_len_rev_timestamp
key: rev_len_rev_timestamp
key_len: 5
ref: NULL
rows: 180270
Extra: Using index condition
1 row in set (0.00 sec)
Index condition pushdown
(ICP) enables the engines to
use extra parts of the index
while avoiding costly row
movements to and from the
SQL layer
Index condition pushdown
(ICP) enables the engines to
use extra parts of the index
while avoiding costly row
movements to and from the
SQL layer
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
98
ICP optimizations
●
Differences in execution time are more significant
when the extra column condition is very selective
(getting 5x the original performance)
●
ICP is ignored when using covering Index,
potentially making the performance worse
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
99
Use of "Handler_*" statistics
• They are post-execution statistics at row level
– Unlike EXPLAIN's "rows" column, they are exact, not a
guess
– They allow comparison of query execution
performance in a deterministic way, independently of
the execution time
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
100
"Handler" Stats (indexed)
mysql> SHOW SESSION STATUS like 'Hand%';
+----------------------------+----------+
| Variable_name | Value |
+----------------------------+----------+
| Handler_commit | 80 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_external_lock | 166 |
| Handler_mrr_init | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 23 |
| Handler_read_key | 736212 |
| Handler_read_last | 0 |
| Handler_read_next | 22208001 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 665215 |
| Handler_read_rnd_next | 14223297 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 66970 |
| Handler_write | 2869409 |
+----------------------------+----------+
18 rows in set (0.00 sec)
Number of times that the first entry
of an index was read. It may indicate
the number of full index scans
Number of times that the first entry
of an index was read. It may indicate
the number of full index scans
Number of times a row
has been retrieved
using an index
Number of times a row
has been retrieved
using an index
Next row has been requested
in index order (typical for
index scans or ranges)
Next row has been requested
in index order (typical for
index scans or ranges)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
101
"Handler" Stats (unindexed)
mysql> SHOW SESSION STATUS like 'Hand%';
+----------------------------+----------+
| Variable_name | Value |
+----------------------------+----------+
| Handler_commit | 80 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_external_lock | 166 |
| Handler_mrr_init | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 23 |
| Handler_read_key | 736212 |
| Handler_read_last | 0 |
| Handler_read_next | 22208001 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 665215 |
| Handler_read_rnd_next | 14223297 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 66970 |
| Handler_write | 2869409 |
+----------------------------+----------+
18 rows in set (0.00 sec)
A row has been requested in a
specific position (typical for joins
or order by without indexes)
A row has been requested in a
specific position (typical for joins
or order by without indexes)
Request to read the
next row in “table
order” (typical for full
table scans)
Request to read the
next row in “table
order” (typical for full
table scans)
Insertions in SELECTS may
indicate temporary tables
Insertions in SELECTS may
indicate temporary tables
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
102
Comparing statistics of the
previous indexes (no indexes)MariaDB [dewiktionary]> FLUSH STATUS;
Query OK, 0 rows affected (0.00 sec)
MariaDB [dewiktionary]> SELECT * FROM revision IGNORE INDEX(rev_page, rev_timestamp, rev_page_rev_timestamp,
rev_timestamp_rev_page) WHERE rev_page = 31579 and rev_timestamp < '2008';
530 rows in set (0.71 sec)
MariaDB [dewiktionary]> SHOW STATUS like 'Hand%';
+----------------------------+---------+
| Variable_name | Value |
+----------------------------+---------+
| Handler_commit | 1 |
| Handler_delete | 0 |
…
| Handler_read_first | 0 |
| Handler_read_key | 0 |
| Handler_read_last | 0 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_deleted | 0 |
| Handler_read_rnd_next | 1430043 |
| Handler_rollback | 0 |
…
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+---------+
27 rows in set (0.00 sec)
Typical result for a
full table scan
Typical result for a
full table scan
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
103
Index on (rev_page)
MariaDB [dewiktionary]> SHOW STATUS like 'Hand%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 1 |
| Handler_delete | 0 |
…
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 4038 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_deleted | 0 |
| Handler_read_rnd_next | 0 |
| Handler_rollback | 0 |
…
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+-------+
27 rows in set (0.01 sec)
Then, scan them one by one in
index order
Then, scan them one by one in
index order
Using the index, request the
first row with rev_page=31579
Using the index, request the
first row with rev_page=31579
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
104
Index on (rev_timestamp)
MariaDB [dewiktionary]> SHOW STATUS like 'Hand%';
+----------------------------+---------+
| Variable_name | Value |
+----------------------------+---------+
| Handler_commit | 1 |
| Handler_delete | 0 |
…
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 199155 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_deleted | 0 |
| Handler_read_rnd_next | 0 |
| Handler_rollback | 0 |
…
+----------------------------+---------+
27 rows in set (0.00 sec)
Then, scan them one by one in
index order (more are matched)
Then, scan them one by one in
index order (more are matched)
Using the index, request the first
row where rev_timestamp<2008
Using the index, request the first
row where rev_timestamp<2008
ICP will be explained later,
let's ignore it for now
ICP will be explained later,
let's ignore it for now
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
105
Index on (rev_page,
rev_timestamp)MariaDB [dewiktionary]> SHOW STATUS like 'Hand%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 1 |
| Handler_delete | 0 |
…
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 530 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_deleted | 0 |
| Handler_read_rnd_next | 0 |
| Handler_rollback | 0 |
…
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+-------+
25 rows in set (0.00 sec)
Rows scanned == Rows returnedRows scanned == Rows returned
With both conditions covered, we
can find the actual first row that
matches the condition using the
index
With both conditions covered, we
can find the actual first row that
matches the condition using the
index
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
106
Index on (rev_timestamp,
rev_page), no ICPMariaDB [dewiktionary]> SHOW STATUS like 'Hand%';
+----------------------------+---------+
| Variable_name | Value |
+----------------------------+---------+
| Handler_commit | 1 |
| Handler_delete | 0 |
…
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 452539 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_deleted | 0 |
| Handler_read_rnd_next | 0 |
| Handler_rollback | 0 |
…
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+---------+
27 rows in set (0.00 sec)
Assuming no ICP, exact same
results as with (rev_timestamp).
The extra column does not help.
Also, EXPLAIN's row count was
very off.
Assuming no ICP, exact same
results as with (rev_timestamp).
The extra column does not help.
Also, EXPLAIN's row count was
very off.
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
107
Index on (rev_timestamp,
rev_page), with ICPMariaDB [dewiktionary]> SHOW STATUS like 'Hand%';
+----------------------------+--------+
| Variable_name | Value |
+----------------------------+--------+
| Handler_commit | 1 |
| Handler_delete | 0 |
…
| Handler_icp_attempts | 452539 |
| Handler_icp_match | 530 |
…
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 530 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_deleted | 0 |
| Handler_read_rnd_next | 0 |
| Handler_rollback | 0 |
…
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+--------+
27 rows in set (0.00 sec)
ICP reduces the number of
‘ENGINE API calls’ significantly,
although making it work more
internally
ICP reduces the number of
‘ENGINE API calls’ significantly,
although making it work more
internally
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
108
Redundant Indexes
●
Creating all 4 previous indexes in production is
not a great idea
– "Left-most index prefix" allows, for example
(rev_page, rev_timestamp) doing everything
you can do with (rev_page)
– If two indexes have equal selectivity, MySQL
chooses the shortest one
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
109
"Left-most index" Example
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision FORCE
INDEX(rev_page_rev_timestamp) WHERE rev_page = 31579G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ref
possible_keys: rev_page_rev_timestamp
key: rev_page_rev_timestamp
key_len: 4
ref: const
rows: 4038
Extra:
1 row in set (0.00 sec)
Only the first column is usedOnly the first column is used
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
110
Duplicate Indexes
●
It is very easy to create indexes with the same
exact definition (same columns and ordering)
– Set a convention for index naming (e.g
tablename_column1_column2_idx) – MySQL does
not allow 2 indexes with the same identifier
– Since MySQL 5.6, an warning is thrown if a
duplicate index is created:
Duplicate index 'page_random2' defined on the table
'dewiktionary.page'. This is deprecated and will be disallowed
in a future release.
Still a warning on 8.0Still a warning on 8.0
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
111
pt-duplicate-index-checker
$ pt-duplicate-key-checker h=localhost,u=root,D=dewiktionary
[…]
# rev_timestamp is a left-prefix of rev_timestamp_rev_page
# Key definitions:
# KEY `rev_timestamp` (`rev_timestamp`),
# KEY `rev_timestamp_rev_page` (`rev_timestamp`,`rev_page`)
# Column types:
# `rev_timestamp` binary(14) not null default '00000000000000'
# `rev_page` int(10) unsigned not null
# To remove this duplicate index, execute:
ALTER TABLE `dewiktionary`.`revision` DROP INDEX `rev_timestamp`;
# rev_page is a left-prefix of rev_page_rev_timestamp
# Key definitions:
# KEY `rev_page` (`rev_page`),
# KEY `rev_page_rev_timestamp` (`rev_page`,`rev_timestamp`),
# Column types:
# `rev_page` int(10) unsigned not null
# `rev_timestamp` binary(14) not null default '00000000000000'
# To remove this duplicate index, execute:
ALTER TABLE `dewiktionary`.`revision` DROP INDEX `rev_page`;
# ############################################# Summary of indexes ########################################
# Size Duplicate Indexes 47836942
# Total Duplicate Indexes 4
# Total Indexes 285
Simple tool to check
redundant and
duplicate indexes
Simple tool to check
redundant and
duplicate indexes
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
112
Invisible Indexes (new in 8.0)
●
Before removing a (believed) redundant or
duplicate index, make it “invisible” to
prevent long-running outages:
mysql-8.0 [dewiktionary]> ALTER TABLE page ALTER INDEX
page_len INVISIBLE;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql-8.0 [dewiktionary]> SELECT * FROM page FORCE
INDEX(page_len);
ERROR 1176 (42000): Key 'page_len' doesn't exist in table
'page'
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
113
"OR"-style conditions over the
same column
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE
rev_page = 790 OR rev_page = 795 OR rev_page = 1024G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys: rev_page,rev_page_rev_timestamp
key: rev_page
key_len: 4
ref: NULL
rows: 201
Extra: Using index condition; Using where
1 row in set (0.01 sec)
Equivalent to:
SELECT * FROM revision WHERE
rev_page IN (790, 795, 1024)
Equivalent to:
SELECT * FROM revision WHERE
rev_page IN (790, 795, 1024)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
114
Handlers on "IN" / "OR"
conditions over the same column+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 1 |
| Handler_delete | 0 |
…
| Handler_prepare | 0 |
| Handler_read_first | 0 |
| Handler_read_key | 3 |
| Handler_read_last | 0 |
| Handler_read_next | 201 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_deleted | 0 |
| Handler_read_rnd_next | 0 |
| Handler_rollback | 0 |
…
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+-------+
25 rows in set (0.00 sec)
Despite identifying themselves
as “range”s, the execution is
slightly different, one index dive
(similar to a ref) is done per
value. This can be an issue in
conditions with thousands of
items.
Despite identifying themselves
as “range”s, the execution is
slightly different, one index dive
(similar to a ref) is done per
value. This can be an issue in
conditions with thousands of
items.
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
115
"OR"-style conditions over
different columns
●
We cannot use a single index efficiently for
both conditions
– We can scan both conditions separately
and mix the results, discarding duplicates
– Or use an index for one condition and not
for the other
– Index merge allows the use of two indexes
for a single table access simultaneously
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
116
Index Merge Example
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE
rev_page = 790 or rev_timestamp < '2004'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: index_merge
possible_keys: rev_page,rev_timestamp,rev_page_rev_timestamp,
rev_timestamp_rev_page
key: rev_page,rev_timestamp
key_len: 4,14
ref: NULL
rows: 190
Extra: Using sort_union(rev_page,rev_timestamp); Using
where
1 row in set (0.00 sec)
Both indexes are used, then
combined using the "union"
operation
Both indexes are used, then
combined using the "union"
operation
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
117
INDEX_MERGE Issues
●
Sometimes it is faster to to execute the
sentence using UNION:
– This is especially true with (UNION ALL)
since MySQL 5.7, if you do not care or
expect duplicates
●
There are also intersection merges, but
multi-column indexes are preferred
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
118
Disabling optimizer features (I)
●
The optimizer_switch variable allows enabling and
disabling globally or per session many query
optimizer features:
MariaDB [nlwiktionary]> SHOW VARIABLES like 'optimizer_switch'G
*************************** 1. row ***************************
Variable_name: optimizer_switch
Value:
index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=
on,index_merge_sort_intersection=off,engine_condition_pushdown=off,index_condition_push
down=on,derived_merge=on,derived_with_keys=on,firstmatch=on,loosescan=on,materializatio
n=on,in_to_exists=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=
on,subquery_cache=on,mrr=off,mrr_cost_based=off,mrr_sort_keys=off,outer_join_with_cache
=on,semijoin_with_cache=on,join_cache_incremental=on,join_cache_hashed=on,join_cache_bk
a=on,optimize_join_buffer_size=off,table_elimination=on,extended_keys=on,exists_to_in=o
n,orderby_uses_equalities=on,condition_pushdown_for_derived=on,split_materialized=on
1 row in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
119
Disabling optimizer features (II)
MariaDB [dewiktionary]> SET optimizer_switch='index_merge_sort_union=off';
Query OK, 0 rows affected (0.00 sec)
MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page =
790 or rev_timestamp < '2004'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ALL
possible_keys:
rev_page,rev_timestamp,rev_page_rev_timestamp,rev_timestamp_rev_page
key: NULL
key_len: NULL
ref: NULL
rows: 1376651
Extra: Using where
1 row in set (0.00 sec)
This will only have effect for
the current session.
This will only have effect for
the current session.
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
120
What happens if we have two
ranges?
●
As seen previously, we cannot use efficiently two
range types on the same table access. Alternatives:
– Use only one index for the most selective column
– Use index condition pushdown to get an
advantage
– Change one of the two ranges into a discrete "IN"
comparison/bucketize with a new column
– Use quadtrees or R-TREEs (spatial indexing)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
121
Example of Bucketizing (I)
MariaDB [dewiktionary]> ALTER TABLE revision DROP INDEX
rev_len_rev_timestamp;
MariaDB [dewiktionary]> EXPLAIN SELECT count(*) FROM revision
WHERE rev_timestamp < '2008' AND rev_len > 5500G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: ALL
possible_keys: rev_timestamp,rev_timestamp_rev_page
key: NULL
key_len: NULL
ref: NULL
rows: 1376651
Extra: Using where
1 row in set (0.00 sec)
Looks like only an index on
(rev_timestamp) or (rev_len)
would be useful as we have 2
ranges.
Looks like only an index on
(rev_timestamp) or (rev_len)
would be useful as we have 2
ranges.
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
122
Example of Bucketizing (II)
MariaDB [dewiktionary]> ALTER TABLE revision ADD COLUMN
rev_len_cat int;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [dewiktionary]> UPDATE revision set rev_len_cat =
IF(rev_len < 10000, rev_len div 1000, 10);
Query OK, 1430042 rows affected (21.18 sec)
Rows matched: 1430042 Changed: 1430042 Warnings: 0
MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX
rev_len_cat_rev_timestamp (rev_len_cat, rev_timestamp);
Query OK, 0 rows affected (7.77 sec)
Records: 0 Duplicates: 0 Warnings: 0
8.0/10.3
instant
alter-nice!
8.0/10.3
instant
alter-nice!
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
123
Example of Bucketizing (III)
MariaDB [dewiktionary]> EXPLAIN SELECT count(*) FROM revision WHERE
rev_timestamp < '2008' AND rev_len > 5500 AND rev_len_cat IN (5, 6,
7, 8, 9, 10)G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys:
rev_timestamp,rev_timestamp_rev_page,rev_len_cat_rev_timestamp
key: rev_len_cat_rev_timestamp
key_len: 19
ref: NULL
rows: 77601
Extra: Using where
1 row in set (0.00 sec)
We did some transformations
to both the structure and the
query.
We did some transformations
to both the structure and the
query.
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
124
Even better: Functional Indexes
(I)
MariaDB [dewiktionary]> ALTER TABLE revision
DROP INDEX rev_len_cat_rev_timestamp,
DROP COLUMN rev_len_cat,
ADD COLUMN rev_len_cat_virtual int as (IF(rev_len < 10000,
rev_len div 1000, 10)) VIRTUAL,
ADD INDEX rev_len_cat_virtual_rev_timestamp
(rev_len_cat_virtual, rev_timestamp);
Query OK, 1430042 rows affected (1 min 42.35 sec)
Records: 1430042 Duplicates: 0 Warnings: 0
Storing the extra content
may be a waste of resources,
lets make it a virtual column
Storing the extra content
may be a waste of resources,
lets make it a virtual column
Indexing
virtual
columns,
new in 10.3
Indexing
virtual
columns,
new in 10.3
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
125
Even better: Functional Indexes
(II)
MariaDB [dewiktionary]> EXPLAIN SELECT count(*) FROM revision WHERE
rev_timestamp < '2008' AND rev_len > 5500 AND rev_len_cat_virtual IN
(5, 6, 7, 8, 9, 10)G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: revision
type: range
possible_keys:
rev_timestamp,rev_timestamp_rev_page,rev_len_cat_virtual_rev_timestamp
key: rev_len_cat_virtual_rev_timestamp
key_len: 19
ref: NULL
rows: 78649
Extra: Using where
1 row in set (0.01 sec)
We did some transformations
to both the structure and the
query.
We did some transformations
to both the structure and the
query.
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
126
MyISAM Internals
node_id versio k v
234234344545 1 leaves deciduous
(empty row)
234234344548 5 gps:latitude 490018321N
234234344549 6 access uncontrolled
(empty row)
234234344551 9 name Big Ben
234234344552 1 source survey
234234344557 1 name London Plane
234234344552 2 source survey
234234344557 2 name London Plane
CarlsbergCarlsberg
recyclingrecycling
Accurate as
of 2010
Accurate as
of 2010
Gwynne
Place
Gwynne
Place
Manor PlaceManor Place
treetree
490018321N490018321N
deciduousdeciduous
London PlaneLondon Plane
surveysurvey
Big BenBig Ben
JOSMJOSM
Northolt RoadNortholt Road
uncontrolleduncontrolled
London
Borough of
Southwark
London
Borough of
Southwark
Data (revision.MYD)Index (part of revision.MYI)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
127
InnoDB Internals (PRIMARY)
Data
clustered
always
using the
primary
key
Data
clustered
always
using the
primary
key
244
208
105
476
356
702
605
811
771
702
476
244
version k v
1 leaves deciduous
3 name Northolt Road
5 gps:latitu 490018321N
6 access uncontrolled
1 editor JOSM
9 name Big Ben
2 source survey
1 name London Plane
7 amenity pub
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
128
InnoDB Internals (Secondary)
6
3
1
1
1
1
3
2
6
5
9
7
PK
356
105
702
811
476
208
Secondary
indexes
contain the
primary key
value
Secondary
indexes
contain the
primary key
value
PK / DataIndex
(version)
244
208
105
476
356
702
605
811
771
702
476
244
version k v
1 leaves deciduous
3 name Northolt Road
5 gps:latitu 490018321N
6 access uncontrolled
1 editor JOSM
9 name Big Ben
2 source survey
1 name London Plane
7 amenity pub
605
244
771
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
129
Consequences of using InnoDB (I)
●
Every table should have a primary key
– If one is not defined, MySQL will choose an
available NOT NULL unique key
– If that is not possible, an internal 6-byte row
identifier will be generated (not user-accesible)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
130
Consequences of using
InnoDB (II)
●
Inserting in primary key order is much faster
– Less fragmentation/page-split
– Usage of "batch" mode, improving insert speed
●
Using auto-increment keys as primary keys can be
a good idea for InnoDB
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
131
Consequences of using
InnoDB (III)
• A very long primary key may increment
substantially the size of secondary keys
– Int or bigint types are recommended instead of UUIDs
or other long strings
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
132
Differences in size
mysql (osm) > CREATE TABLE
pk_int (id int PRIMARY KEY
auto_increment,
a int,
b int,
c int,
d int);
Query OK, 0 rows affected
(0.16 sec)
mysql (osm) > CREATE TABLE
pk_uuid (id char(36)
PRIMARY KEY,
a int,
b int,
c int,
d int);
Query OK, 0 rows affected
(0.04 sec)
pk_int pk_uuid
0
8500000
17000000
25500000
34000000
42500000
12,582,912
20,971,520
13,631,488
32,505,856
.ibd size (no secondary indexes) .ibd size (with secondary indexes)
bytes
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
133
Extended primary key
optimization
●
As the primary key is part of all secondary keys,
this can be used “for free”:
– For row filtering (since MySQL 5.6)
– To return results in primary key order
– To avoid reading data from the table (covering
index)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
134
Extended Primary Key Example
mysql (osm) > EXPLAIN SELECT node_id FROM nodes WHERE
changeset_id = 24284 and node_id <> 146472942G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: nodes
type: range
possible_keys: PRIMARY,changeset_id_idx
key: changeset_id_idx
key_len: 16
ref: NULL
rows: 50
Extra: Using where; Using index
1 row in set (0.07 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
135
JOINS
Query Optimization with MySQL 8.0 and MariaDB
10.3: The Basics
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
136
(Block) Nested Loop Join
●
Until MySQL 5.5 there was only one algorithm to
execute a JOIN:
node_id version lat lon
1 1 52 0.5
1 2 52 0.5
2 1 51 1
3 1 53 1.5
node_id version k v
1 1 name Big Benn
1 1 tourism attraction
1 2 name Big Ben
1 2 tourism attraction
3 1 name London Eye
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
137
Extra Access type: eq_ref
mysql (osm) > EXPLAIN SELECT * FROM nodes JOIN node_tags
USING(node_id, version) WHERE node_tags.v= 'Big Ben'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: node_tags
type: ref
possible_keys: PRIMARY,v_idx
key: v_idx
key_len: 767
ref: const
rows: 1
Extra: Using where; Using index
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: nodes
type: eq_ref
possible_keys: PRIMARY,version_idx
key: PRIMARY
key_len: 16
ref: osm.node_tags.node_id,osm.node_tags.version
rows: 1
Extra: NULL
2 rows in set (0.00 sec)
eq_ref is similar to ref, but
allows faster JOINS
because, by using a unique
key, it only has to search
one row for each previous
result
eq_ref is similar to ref, but
allows faster JOINS
because, by using a unique
key, it only has to search
one row for each previous
result
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
138
JOIN Optimization
●
Two main goals:
– Perform an effective filtering on each table
access, if possible using indexes
– Perform the access in the most efficient table
order
●
When joining 3 or more tables in a star schema,
the "covering index" strategy can have a huge
impact
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
139
Example: optimize this JOIN (I)
SELECT n.node_id, n.latitude, n.longitude
FROM way_nodes w_n
JOIN way_tags w_t
ON w_n.way_id = w_t.way_id and
w_n.version = w_t.version
JOIN nodes n
ON w_n.node_id = n.node_id
JOIN node_tags n_t
ON n.node_id = n_t.node_id and
n.version = n_t.version
WHERE w_t.k = 'building' and
n_t.k = 'entrance' and
n_t.v = 'main';
We start without
secondary indexes
We start without
secondary indexes
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
140
Example: optimize this JOIN (II)
************************ 1. row ************************
id: 1
select_type: SIMPLE
table: w_t
type: index
possible_keys: PRIMARY
key: PRIMARY
key_len: 783
ref: NULL
rows: 1335702
Extra: Using where; Using index
************************ 2. row ************************
id: 1
select_type: SIMPLE
table: w_n
type: ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 16
ref: osm.w_t.way_id,osm.w_t.version
rows: 3
Extra: NULL
mysql (osm) > SELECT …
858 rows in set (9.00 sec)
************************ 3. row ************************
id: 1
select_type: SIMPLE
table: n_t
type: ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: osm.w_n.node_id
rows: 1
Extra: Using where
************************ 4. row ************************
id: 1
select_type: SIMPLE
table: n
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 16
ref: osm.w_n.node_id,osm.n_t.version
rows: 1
Extra: Using index
4 rows in set (0.01 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
141
Example: optimize this JOIN (III)
mysql (osm) > ALTER TABLE way_tags
ADD INDEX k_idx(k);
Query OK, 0 rows affected (4.80 sec)
Records: 0 Duplicates: 0 Warnings: 0
Creating an index
on way_tags.k
Creating an index
on way_tags.k
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
142
Example: optimize this JOIN (IV)
********************** 1. row **********************
id: 1
select_type: SIMPLE
table: w_t
type: ref
possible_keys: PRIMARY,k_idx
key: k_idx
key_len: 767
ref: const
rows: 452274
Extra: Using where; Using index
********************** 2. row **********************
id: 1
select_type: SIMPLE
table: w_n
type: ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 16
ref: osm.w_t.way_id,osm.w_t.version
rows: 3
Extra: NULL
mysql (osm) > SELECT …
858 rows in set (8.58 sec)
It seems like the index
is not very useful
It seems like the index
is not very useful
********************** 3. row **********************
id: 1
select_type: SIMPLE
table: n_t
type: ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: osm.w_n.node_id
rows: 1
Extra: Using where
********************** 4. row **********************
id: 1
select_type: SIMPLE
table: n
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 16
ref: osm.w_n.node_id,osm.n_t.version
rows: 1
Extra: NULL
4 rows in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
143
Example: optimize this JOIN (V)
mysql (osm) > ALTER TABLE node_tags
ADD INDEX k_idx(k);
Query OK, 0 rows affected (2.82 sec)
Records: 0 Duplicates: 0 Warnings: 0
The order does not seem
to be adequate, let's try
adding an index to start
by accessing node_tags
The order does not seem
to be adequate, let's try
adding an index to start
by accessing node_tags
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
144
Example: optimize this JOIN (VI)
********************** 1. row **********************
id: 1
select_type: SIMPLE
table: w_t
type: ref
possible_keys: PRIMARY,k_idx
key: k_idx
key_len: 767
ref: const
rows: 452274
Extra: Using where; Using index
********************** 2. row **********************
id: 1
select_type: SIMPLE
table: w_n
type: ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 16
ref: osm.w_t.way_id,osm.w_t.version
rows: 3
Extra: NULL
mysql (osm) > SELECT …
858 rows in set (7.33 sec)
********************** 3. row **********************
id: 1
select_type: SIMPLE
table: n_t
type: ref
possible_keys: PRIMARY,k_idx
key: PRIMARY
key_len: 8
ref: osm.w_n.node_id
rows: 1
Extra: Using where
********************** 4. row **********************
id: 1
select_type: SIMPLE
table: n
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 16
ref: osm.w_n.node_id,osm.n_t.version
rows: 1
Extra: NULL
4 rows in set (0.00 sec)
It keeps using
the wrong
order, even if
we delete the
w_t.k_idx
index
It keeps using
the wrong
order, even if
we delete the
w_t.k_idx
index
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
145
Example: optimize this JOIN (VII)
SELECT STRAIGHT_JOIN n.node_id, n.latitude, n.longitude
FROM node_tags n_t
JOIN nodes n
ON n.node_id = n_t.node_id and
n.version = n_t.version
JOIN way_nodes w_n
ON w_n.node_id = n.node_id
JOIN way_tags w_t
ON w_n.way_id = w_t.way_id and
w_n.version = w_t.version
WHERE w_t.k = 'building' and
n_t.k = 'entrance' and
n_t.v = 'main';
Let's see why rewriting it
into this query
Let's see why rewriting it
into this query
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
146
Example: optimize this JOIN (VIII)
********************** 1. row **********************
id: 1
select_type: SIMPLE
table: n_t
type: ref
possible_keys: PRIMARY,k_idx
key: k_idx
key_len: 767
ref: const
rows: 2390
Extra: Using index condition; Using where
********************** 2. row **********************
id: 1
select_type: SIMPLE
table: n
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 16
ref: osm.n_t.node_id,osm.n_t.version
rows: 1
Extra: NULL
There is no index
on w_n that would
allow efficient
access
There is no index
on w_n that would
allow efficient
access
********************** 3. row **********************
id: 1
select_type: SIMPLE
table: w_n
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 3597858
Extra: Using where; Using join buffer (Block
Nested Loop)
********************** 4. row **********************
id: 1
select_type: SIMPLE
table: w_t
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 783
ref: osm.w_n.way_id,osm.w_n.version,const
rows: 1
Extra: Using where; Using index
4 rows in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
147
Example: optimize this JOIN (IX)
mysql (osm) > ALTER TABLE way_nodes
ADD INDEX node_id_idx(node_id);
Query OK, 0 rows affected (17.77 sec)
Records: 0 Duplicates: 0 Warnings: 0
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
148
Example: optimize this JOIN (X)
********************** 1. row **********************
id: 1
select_type: SIMPLE
table: n_t
type: ref
possible_keys: PRIMARY,k_idx
key: k_idx
key_len: 767
ref: const
rows: 2390
Extra: Using index condition; Using where
********************** 2. row **********************
id: 1
select_type: SIMPLE
table: n
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 16
ref: osm.n_t.node_id,osm.n_t.version
rows: 1
Extra: NULL
mysql (osm) > SELECT …
858 rows in set (0.73 sec)
Now it starts with
the right table
(without
STRAIGHT_JOIN)
Now it starts with
the right table
(without
STRAIGHT_JOIN)
********************** 3. row **********************
id: 1
select_type: SIMPLE
table: w_n
type: ref
possible_keys: PRIMARY,node_id_idx
key: node_id_idx
key_len: 8
ref: osm.n_t.node_id
rows: 1
Extra: Using index
********************** 4. row **********************
id: 1
select_type: SIMPLE
table: w_t
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 783
ref: osm.w_n.way_id,osm.w_n.version,const
rows: 1
Extra: Using where; Using index
4 rows in set (0.04 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
149
Example: optimize this JOIN (XI)
************************* 1. row *************************
id: 1
select_type: SIMPLE
table: n_t
type: ref
possible_keys: PRIMARY,k_idx,k_v_idx
key: k_v_idx
key_len: 1534
ref: const,const
rows: 900
Extra: Using where; Using index
************************* 2. row *************************
id: 1
select_type: SIMPLE
table: n
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 16
ref: osm.n_t.node_id,osm.n_t.version
rows: 1
Extra: NULL
mysql (osm) > SELECT ...
858 rows in set (0.02 sec)
An index on (k,v) is
even better
An index on (k,v) is
even better
************************ 3. row ************************
id: 1
select_type: SIMPLE
table: w_n
type: ref
possible_keys: PRIMARY,node_id_idx
key: node_id_idx
key_len: 8
ref: osm.n_t.node_id
rows: 1
Extra: Using index
************************ 4. row ************************
id: 1
select_type: SIMPLE
table: w_t
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 783
ref: osm.w_n.way_id,osm.w_n.version,const
rows: 1
Extra: Using where; Using index
4 rows in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
150
“New” JOIN methods
●
MySQL 5.6 added:
– Batch Key Access
●
MariaDB has since 5.3:
– Batch Key Access
– Hash Joins
– Slightly modified versions of the above ones
(with “incremental” buffers to join 3 or more
tables)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
151
Multi-range read
• This optimization orders results obtained
from a secondary key in primary key/physical
order before accessing the rows
– It may help execution time of queries when disk-bound
– It requires tuning of the read_rnd_buffer_size (size of
the buffer used for ordering the results)
• BKA JOINs are based on the mrr optimization
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
152
MRR Example (I)
mysql (osm) > EXPLAIN SELECT * FROM nodes WHERE timestamp >= '2013-07-
01 00:00:00' AND timestamp < '2014-01-01 00:00:00'G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: nodes
type: range
possible_keys: nodes_timestamp_idx
key: nodes_timestamp_idx
key_len: 5
ref: NULL
rows: 429684
Extra: Using index condition; Using MRR
1 row in set (0.02 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
153
MRR example (II)
[restart]
mysql> SET optimizer_switch='mrr=off';
mysql> SELECT * FROM nodes WHERE timestamp >= '2013-07-01 00:00:00' AND timestamp <
'2014-01-01 00:00:00';
205617 rows in set (5.16 sec)
mysql> SELECT * FROM nodes WHERE timestamp >= '2013-07-01 00:00:00' AND timestamp <
'2014-01-01 00:00:00';
205617 rows in set (0.60 sec)
[restart]
mysql> SET read_rnd_buffer_size=50 * 1024 * 1024;
mysql> SELECT * FROM nodes WHERE timestamp >= '2013-07-01 00:00:00' AND timestamp <
'2014-01-01 00:00:00';
205617 rows in set (2.39 sec)
mysql> SELECT * FROM nodes WHERE timestamp >= '2013-07-01 00:00:00' AND timestamp <
'2014-01-01 00:00:00';
205617 rows in set (0.73 sec)
“Cold” results are
significantly better with
mrr (but it can impact
negatively, too)
“Cold” results are
significantly better with
mrr (but it can impact
negatively, too)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
154
Batch Key Access
node_id version lat lon
1 1 52 0.5
1 2 52 0.5
2 1 51 1
3 1 53 1.5
node_id version k v
1 1 name Big Benn
1 1 tourism attraction
1 2 name Big Ben
1 2 tourism attraction
3 1 amenity cafe
Buffer
sorted in
physical/PK
order
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
155
Hash Joins
• Only work for equi-joins
node_id version lat lon
1 1 52 0.5
1 2 52 0.5
2 1 51 1
3 1 53 1.5
node_id version k v
1 1 name Big Benn
1 1 tourism attraction
1 2 name Big Ben
1 2 tourism attraction
3 1 amenity cafe
Hash table
for faster
access to
rows
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
156
MySQL Configuration
• BKA requires changes of default optimizer
configuration:
mysql-8.0 (osm) > SET optimizer_switch= 'mrr=on';
mysql-8.0 (osm) > SET optimizer_switch= 'mrr_cost_based=off';
mysql-8.0 (osm) > SET optimizer_switch= 'batch_key_access=on';
– Additionally, configuring the join_buffer_size adequately
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
157
MariaDB configuration
mariadb-10.3 (osm) > SET optimizer_switch = 'join_cache_incremental=on';
mariadb-10.3 (osm) > SET optimizer_switch = 'join_cache_hashed=on';
mariadb-10.3 (osm) > SET optimizer_switch = 'join_cache_bka=on';
- Enabled by default
mariadb-10.3 (osm) > SET join_cache_level = 3 (for hash joins)
mariadb-10.3 (osm) > SET join_cache_level = 5 (for BKA)
- Also, configure join_buffer_size appropriately.
- Hash joins, like BKA, are highly dependent on disk-bound DBs to be
effective due to the extra overhead
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
158
Nested Loop Join (cold buffers
buffer_pool=100MB, join_buffer=4M)
mariadb-10.3 (osm) > EXPLAIN SELECT changeset_id,
count(*) FROM changesets JOIN nodes on
changesets.id = nodes.changeset_id GROUP BY
visibleG
******************** 1. row ********************
id: 1
select_type: SIMPLE
table: changesets
type: index
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: NULL
rows: 69115
Extra: Using index; Using temporary; Using
filesort
******************** 2. row ********************
id: 1
select_type: SIMPLE
table: nodes
type: ref
possible_keys: changeset_id
key: changeset_id
key_len: 8
ref: osm.changesets.id
rows: 19
Extra:
2 rows in set (0.00 sec)
mariadb-10.3 (osm) > SELECT visible, count(*) FROM
changesets JOIN nodes on changesets.id =
nodes.changeset_id GROUP BY visible;
+---------+----------+
| visible | count(*) |
+---------+----------+
| 1 | 2865312 |
+---------+----------+
1 row in set (32.86 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
159
Hash Join (cold buffers,
buffer_pool=100M, join_buffer=4M)
mariadb-10.3 (osm) > EXPLAIN SELECT
changeset_id, count(*) FROM changesets JOIN
nodes on changesets.id = nodes.changeset_id
GROUP BY visibleG
******************* 1. row *******************
id: 1
select_type: SIMPLE
table: changesets
type: index
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: NULL
rows: 69115
Extra: Using index; Using temporary;
Using filesort
******************** 2. row ********************
id: 1
select_type: SIMPLE
table: nodes
type: hash_ALL
possible_keys: changeset_id
key: #hash#changeset_id
key_len: 8
ref: osm.changesets.id
rows: 2781732
Extra: Using join buffer (flat, BNLH join)
2 rows in set (0.00 sec)
mariadb-10.3 (osm) > SELECT visible, count(*) FROM
changesets JOIN nodes on changesets.id =
nodes.changeset_id GROUP BY visible;
+---------+----------+
| visible | count(*) |
+---------+----------+
| 1 | 2865312 |
+---------+----------+
1 row in set (6.66 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
160
SUBQUERIES
Query Optimization with MySQL 8.0 and MariaDB
10.3: The Basics
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
161
Access types:
unique_subquery/index_subquery
mysql-8.0 (osm) > EXPLAIN SELECT *
FROM node_tags WHERE v = 'Big Ben'
and node_id NOT IN (SELECT node_id
FROM nodes WHERE tile < 100000000)G
************** 1. row **************
id: 1
select_type: PRIMARY
table: node_tags
type: ref
possible_keys: v_idx
key: v_idx
key_len: 767
ref: const
rows: 1
Extra: Using where; Using
index
Unique
subquery
is similar,
but using a
unique or
primary
key
Unique
subquery
is similar,
but using a
unique or
primary
key
************** 2. row **************
id: 2
select_type: DEPENDENT SUBQUERY
table: nodes
type: index_subquery
possible_keys: PRIMARY,nodes_tile_idx
key: PRIMARY
key_len: 8
ref: func
rows: 1
Extra: Using where
2 rows in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
162
Subqueries in MySQL
●
MySQL versions traditionally had very bad press
regarding subquries
– It was common to recommend rewriting them
(when possible) into JOINS
●
Since MySQL 5.6, its query execution plans have
improved significantly
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
163
Lazy Materialization of derived
tables
●
Option available since MySQL 5.6
– Improves the execution time of EXPLAIN (it no
longer needs to execute subqueries)
– Derived tables can be indexed automatically at
execution time to improve its performance
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
164
Derived Table Example
mysql-5.5.40 (osm) > EXPLAIN SELECT count(*) FROM (SELECT * FROM nodes WHERE VISIBLE = 1) n JOIN changesets ON
n.changeset_id = changesets.id;
+----+-------------+------------+--------+---------------+---------+---------+----------------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+---------+---------+----------------+---------+-------------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 2865312 | |
| 1 | PRIMARY | changesets | eq_ref | PRIMARY | PRIMARY | 8 | n.changeset_id | 1 | Using index |
| 2 | DERIVED | nodes | ALL | NULL | NULL | NULL | NULL | 2865521 | Using where |
+----+-------------+------------+--------+---------------+---------+---------+----------------+---------+-------------+
3 rows in set (1.42 sec)
mysql-8.0 (osm) > EXPLAIN SELECT count(*) FROM (SELECT * FROM nodes WHERE VISIBLE = 1) n JOIN changesets ON
n.changeset_id = changesets.id;
+----+-------------+------------+-------+---------------+-------------+---------+-------------------+---------
+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
|
+----+-------------+------------+-------+---------------+-------------+---------+-------------------+---------
+-------------+
| 1 | PRIMARY | changesets | index | PRIMARY | PRIMARY | 8 | NULL | 70917 | Using
index |
| 1 | PRIMARY | <derived2> | ref | <auto_key0> | <auto_key0> | 8 | osm.changesets.id | 40 | NULL
|
| 2 | DERIVED | nodes | ALL | NULL | NULL | NULL | NULL | 2853846 | Using
where |
+----+-------------+------------+-------+---------------+-------------+---------+-------------------+---------
+-------------+
3 rows in set (0.00 sec)
Subquery is
not
executed
Subquery is
not
executed
Auto-
generated
index
Auto-
generated
index
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
165
A Common 5.5 Performance
Problemmysql-5.5.40 (osm) > EXPLAIN SELECT * FROM nodes
WHERE nodes.changeset_id IN (
SELECT changesets.id
FROM changesets
JOIN users
ON changesets.user_id = users.id and users.display_name = 'Steve');
+----+--------------------+------------+--------+--------------------------------+------------------------+---------+-------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------+--------+--------------------------------+------------------------+---------+-------+---------+-------------+
| 1 | PRIMARY | nodes | ALL | NULL | NULL | NULL | NULL | 2865521 | Using where |
| 2 | DEPENDENT SUBQUERY | users | const | PRIMARY,users_display_name_idx | users_display_name_idx | 767 | const | 1 | Using index |
| 2 | DEPENDENT SUBQUERY | changesets | eq_ref | PRIMARY | PRIMARY | 8 | func | 1 | Using where |
+----+--------------------+------------+--------+--------------------------------+------------------------+---------+-------+---------+-------------+
3 rows in set (0.00 sec)
mysql-5.5.40 (osm) > SELECT ...;
+-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+
| node_id | latitude | longitude | changeset_id | visible | timestamp | tile | version |
+-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+
| 99890 | 515276425 | -1497621 | 552 | 1 | 2005-10-25 00:35:24 | 2062268512 | 1 |
| 109174 | 515364532 | -1457329 | 1875 | 1 | 2006-01-20 00:01:27 | 2062268519 | 1 |
| 276538 | 515324296 | -2094688 | 810 | 1 | 2005-11-25 21:42:53 | 2062267078 | 1 |
| 442987 | 515449207 | -1275650 | 1941 | 1 | 2006-01-22 23:50:28 | 2062268628 | 1 |
| 442988 | 515449741 | -1272860 | 1941 | 1 | 2006-01-22 23:50:29 | 2062268628 | 1 |
| 498803 | 515438432 | -1269436 | 2171 | 1 | 2006-02-03 21:55:17 | 2062268628 | 1 |
| 138212838 | 513010180 | -1699929 | 7757299 | 1 | 2011-04-03 18:14:14 | 2062220563 | 6 |
+-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+
7 rows in set (2.60 sec)
This means that
the subquery is
executed almost
3 million times
This means that
the subquery is
executed almost
3 million times
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
166
Semijoin Optimization
●
The only way to execute certain IN subqueries was to
execute them with poor strategy
– This forced rewriting of certain queries into JOINS or scalar subqueries,
when possible
●
There are now several additional options (many
automatic):
– Convert to a JOIN
– Materialization (including index creation)
– FirstMatch
– LooseScan
– Duplicate Weedout
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
167
The Previous Query is Not a Problem
in 5.6+/MariaDB 5.3+
mysql-8.0 (osm) > EXPLAIN SELECT * FROM nodes
WHERE nodes.changeset_id IN (
SELECT changesets.id
FROM change sets
JOIN users
ON changesets.user_id = users.id and users.display_name = 'Steve');
+----+-------------+------------+-------+--------------------------------+------------------------+---------+-------------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------+--------------------------------+------------------------+---------+-------------------+-------+-------------+
| 1 | SIMPLE | users | const | PRIMARY,users_display_name_idx | users_display_name_idx | 767 | const | 1 | Using index |
| 1 | SIMPLE | changesets | ALL | PRIMARY | NULL | NULL | NULL | 70917 | Using where |
| 1 | SIMPLE | nodes | ref | changeset_id | changeset_id | 8 | osm.changesets.id | 21 | NULL |
+----+-------------+------------+-------+--------------------------------+------------------------+---------+-------------------+-------+-------------+
3 rows in set (0.00 sec)
mysql-8.0 (osm) > SELECT ...;
+-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+
| node_id | latitude | longitude | changeset_id | visible | timestamp | tile | version |
+-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+
| 99890 | 515276425 | -1497621 | 552 | 1 | 2005-10-25 00:35:24 | 2062268512 | 1 |
...
| 138212838 | 513010180 | -1699929 | 7757299 | 1 | 2011-04-03 18:14:14 | 2062220563 | 6 |
+-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+
7 rows in set (0.02 sec)
Executed as
a regular
JOIN
Executed as
a regular
JOIN
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
168
First Match Strategy
mysql-8.0 (osm) > EXPLAIN SELECT * FROM changesets WHERE id IN (SELECT changeset_id FROM nodes)G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: changesets
partitions: NULL
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 70917
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: nodes
partitions: NULL
type: ref
possible_keys: changeset_id
key: changeset_id
key_len: 8
ref: osm.changesets.id
rows: 33
filtered: 100.00
Extra: Using index; FirstMatch(changesets)
2 rows in set, 1 warning (0.00 sec)
It is converting
the ref into an
eq_ref, shot-
circuiting the
execution
It is converting
the ref into an
eq_ref, shot-
circuiting the
execution
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
169
Enabling and disabling
materialization, semijoin, etc
mysql-8.0 (osm) > SHOW VARIABLES like 'optimizer_switch'G
*************************** 1. row ***************************
Variable_name: optimizer_switch
Value:
index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_in
tersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=o
n,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materializat
ion=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_m
aterialization_cost_based=on,use_index_extensions=on,condition_fanout_filter
=on,derived_merge=on
1 row in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
170
FULLTEXT, GEODATA
SEARCH AND JSON SUPPORT
Query Optimization with MySQL 8.0 and MariaDB
10.3: The Basics
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
171
Fuzzy Search of “hotel/Grand
Hotel/Hotel X/etc”
●
“Typical” way to solve this:
mysql (osm) >
SELECT way_id as id, v
FROM way_tags
WHERE v like '%otel%';
2125 rows in set (3.19 sec)
Too slowToo slow
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
172
Let’s Add an Index
mysql (osm) > ALTER TABLE way_tags ADD INDEX(v);
Query OK, 0 rows affected (23.96 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql (osm) > SELECT ...;
2125 rows in set (2.75 sec)
mysql (osm) > EXPLAIN SELECT way_id as id, v FROM way_tags
WHERE v like '%otel%';
+----+.+-------+---------------+------+---------+------+---------+----------+--------------------------+
| id |.| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+.+-------+---------------+------+---------+------+---------+----------+--------------------------+
| 1 |.| index | NULL | v | 769 | NULL | 8964000 | 11.11 | Using where; Using index |
+----+.+-------+---------------+------+---------+------+---------+----------+--------------------------+
1 row in set, 1 warning (0.01 sec)
Still slow, why?Still slow, why?
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
173
Fulltext Index
mysql (osm) > ALTER TABLE way_tags add FULLTEXT index(v);
Query OK, 0 rows affected (53.53 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql (osm) > SELECT 'way' as type, way_id as id, v FROM
way_tags WHERE MATCH(v) AGAINST ('+hotel*' IN BOOLEAN MODE);
1851 rows in set (0.04 sec)
mysql (osm) > EXPLAIN …;
+----+.+----------+---------------+------+---------+-----+------+----------+-----------------------------------+
| id |.| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+.+----------+---------------+------+---------+-----+------+----------+-----------------------------------+
| 1 |.| fulltext | v | v | 0 | | 1 | 100.00 | Using where; Ft_hints: no_ranking |
+----+.+----------+---------------+------+---------+-----+------+----------+-----------------------------------+
1 row in set, 1 warning (0.00 sec)
Some fields are
useless for FULTEXT
Some fields are
useless for FULTEXT
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
174
‘Newer’ Fulltext Optimizations
mysql-5.5 (osm) > EXPLAIN SELECT count(*) FROM way_tags_myisam WHERE MATCH(v) AGAINST('hotel');
+----+-------------+-----------------+----------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------------+----------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | way_tags_myisam | fulltext | v | v | 0 | | 1 | Using where |
+----+-------------+-----------------+----------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql-5.5 (osm) > SHOW STATUS like 'Hand%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 0 |
| Handler_delete | 0 |
...
| Handler_read_first | 0 |
| Handler_read_key | 0 |
| Handler_read_last | 0 |
| Handler_read_next | 425 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
...
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+-------+
16 rows in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
175
Newer Fulltext Optimizations
(cont.)mysql-8.0 (osm) > EXPLAIN SELECT count(*) FROM way_tags WHERE MATCH(v) AGAINST('hotel');
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Select tables optimized away |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql-8.0 (osm) > SHOW STATUS like 'Hand%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 1 |
| Handler_delete | 0 |
...
| Handler_read_first | 0 |
| Handler_read_key | 0 |
| Handler_read_last | 0 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
...
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+-------+
18 rows in set (0.00 sec)
It’s counting
directly from
the
FULLTEXT
index
It’s counting
directly from
the
FULLTEXT
index
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
176
Open Issues and Limitations
●
No postfix support (wildcards)
●
Simple Ranking (and different from MyISAM)
●
No stemming support
●
Some multi-language limitations
More on FULLTEXT InnoDB support:
http://www.drdobbs.com/database/full-text-search-with-innodb/231902587
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
177
Alternatives
●
Apache Lucene
– Solr
– Elasticsearch
●
Sphinx
– SphinxSE
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
178
Find the Closest Starbucks
(Spatial Search)mysql (osm) > SET @lat:=50.11745057; SET @lon:= 8.62699463;
mysql (osm) > SELECT n.node_id,
n.longitude/10000000 as longitude,
n.latitude/10000000 as latitude,
sqrt(pow((latitude/10000000 - @lat) * 111231.29, 2) +
pow((longitude/10000000 - @lon) * 71520.91, 2))
as `distance in metres`
FROM nodes n
JOIN node_tags n_t1
ON n.node_id = n_t1.node_id
JOIN node_tags n_t2
ON n.node_id = n_t2.node_id
WHERE
n_t1.k = 'amenity' and
n_t1.v = 'cafe' and
n_t2.k = 'name' and
n_t2.v like 'Starbucks%'
ORDER BY `distance in metres` ASC
LIMIT 1;
+------------+-----------+----------+--------------------+
| node_id | longitude | latitude | distance in metres |
+------------+-----------+----------+--------------------+
| 3136353405 | 8.6523 | 50.1093 | 2023.6465372574328 |
+------------+-----------+----------+--------------------+
1 row in set, 36 warnings (0.81 sec)
You are hereYou are here
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
179
This Query is Slow
mysql [osm]> EXPLAIN SELECT ...G
**************** 1. row ***************
Id: 1
select_type: SIMPLE
table: n_t1
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 2210950
Extra: Using where;
Using temporary; Using filesort
*********************** 2. row ***********************
id: 1
select_type: SIMPLE
table: n_t2
type: ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: osm.n_t1.node_id
rows: 1
Extra: Using where
*********************** 3. row ***********************
id: 1
select_type: SIMPLE
table: n
type: ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: osm.n_t1.node_id
rows: 1
Extra:
3 rows in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
180
Can We Optimize it?
• We could add a bounding box:
mysql (osm) > ALTER TABLE nodes ADD INDEX (latitude, longitude);
mysql (osm) > SELECT n.node_id,
n.longitude/10000000 as longitude,
n.latitude/10000000 as latitude,
sqrt(pow((latitude/10000000 - @lat) * 111231.29, 2) +
pow((longitude/10000000 - @lon) * 71520.91, 2))
as `distance in metres`
FROM nodes n
JOIN node_tags n_t1
ON n.node_id = n_t1.node_id
JOIN node_tags n_t2
ON n.node_id = n_t2.node_id
WHERE
n_t1.k = 'amenity' and
n_t1.v = 'cafe' and
n_t2.k = 'name' and
n_t2.v like 'Starbucks%' and
n.latitude BETWEEN ((@lat - 1000/111231.29) * 10000000)
AND ((@lat + 1000/111231.29) * 10000000) and
n.longitude BETWEEN ((@lon - 1000/71520.91) * 10000000)
AND ((@lon + 1000/71520.91) * 10000000)
ORDER BY `distance in metres` ASC
LIMIT 1;
This is not a
square, only an
approximation
This is not a
square, only an
approximation
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
181
We Create an Index… and Force It
mysql-8.0 (osm) > EXPLAIN SELECT ...;
+----+-------------+-------+------------+------+----------+---------+------+------------------+---------+----------+------------------+
| id | select_type | table | partitions | type | possible | key | key_ | ref | rows | filtered | Extra |
| | | | | | _keys | | len | | | | |
+----+-------------+-------+------------+------+----------+---------+------+------------------+---------+----------+------------------+
| 1 | SIMPLE | n_t1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 2210950 | 0.00 | Using where; |
| | | | | | | | | | | | Using temporary; |
| | | | | | | | | | | | Using filesort |
| 1 | SIMPLE | n | NULL | ref | PRIMARY, | PRIMARY | 8 | osm.n_t1.node_id | 1 | 5.00 | Using where |
| | | | | | latitude | | | | | | |
| 1 | SIMPLE | n_t2 | NULL | ref | PRIMARY | PRIMARY | 8 | osm.n_t1.node_id | 1 | 1.41 | Using where |
+----+-------------+-------+------------+------+----------+---------+------+------------------+---------+----------+------------------+
3 rows in set, 1 warning (0.00 sec)
mysql-8.0 (osm) > EXPLAIN SELECT ... FROM nodes n FORCE INDEX(latitude) ...;
+----+-------------+-------+------------+-------+----------+----------+------+---------------+--------+----------+------------------+
| id | select_type | table | partitions | type | possible | key | key_ | ref | rows | filtered | Extra |
| | | | | | _keys | | len | | | | |
+----+-------------+-------+------------+-------+----------+----------+------+---------------+--------+----------+------------------+
| 1 | SIMPLE | n | NULL | range | latitude | latitude | 8 | NULL | 949370 | 11.11 | Using where; |
| | | | | | | | | | | | Using index; |
| | | | | | | | | | | | Using filesort |
| 1 | SIMPLE | n_t1 | NULL | ref | PRIMARY | PRIMARY | 8 | osm.n.node_id | 1 | 1.41 | Using where |
| 1 | SIMPLE | n_t2 | NULL | ref | PRIMARY | PRIMARY | 8 | osm.n.node_id | 1 | 1.41 | Using where |
+----+-------------+-------+------------+-------+----------+----------+------+---------------+--------+----------+------------------+
3 rows in set, 1 warning (0.00 sec)
Mysql-8.0 (osm) > SELECT ... FROM nodes n FORCE INDEX(latitude) ...;
0 rows in set (0.26 sec)
Still many
rows are
examined
Still many
rows are
examined
Most of the gain
comes from the
covering index, not
the filtering
Most of the gain
comes from the
covering index, not
the filtering
MySQL ignores the
newly created index,
why?
MySQL ignores the
newly created index,
why?
Performance
improvement is
not great
Performance
improvement is
not great
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
182
Multiple Range Scans Cannot Be
Optimized with BTREE Indexes
●
We need quadtrees or R-TREE Indexes for
indexing in multiple dimensions
– The latter are implemented in MySQL with
the name “SPATIAL indexes”, as they only
apply to GIS types
●
Spatial indexing is available on MySQL since
5.7.5
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
183
Creating a Spatial Index
mysql (osm) > ALTER TABLE nodes
ADD COLUMN coord GEOMETRY NOT NULL SRID 4326;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql (osm) > UPDATE nodes
SET coord = point(longitude/10000000,
latitude/10000000);
Query OK, 20216490 rows affected (7 min 10.28 sec)
Rows matched: 20216490  Changed: 20216490  Warnings: 0
mysql (osm) > ALTER TABLE nodes add SPATIAL index(coord);
Query OK, 0 rows affected (1 hour 7 min 13.29 sec)
Records: 0 Duplicates: 0 Warnings: 0
This is new since
5.7
This is new since
5.7
Instant, yay!
(or create it
VIRTUAL)
Instant, yay!
(or create it
VIRTUAL)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
184
New Query
mysql> SET @area := ST_Envelope(linestring(POINT(@lon - 500/71520.91, @lat - 500/111231.29), POINT(@lon +
500/71520.91, @lat + 500/111231.29)));
mysql> SELECT n.node_id,
x(n.coord) as longitude,
y(n.coord) as latitude,
st_distance(POINT(@lon, @lat), coord) as distance
FROM nodes n
JOIN node_tags n_t1
ON n.node_id = n_t1.node_id
JOIN node_tags n_t2
ON n.node_id = n_t2.node_id
WHERE
n_t1.k = 'amenity' and
n_t1.v = 'cafe' and
n_t2.k = 'name' and
n_t2.v like 'Starbucks%' and
st_within(coord, @area)
ORDER BY st_distance(POINT(@lon, @lat), coord) ASC
LIMIT 1;
We can use any
shape we want
thanks to 5.6
improvements
We can use any
shape we want
thanks to 5.6
improvements
Also substitute
functions like
Envelope with
ST_Envelope
since 5.7
Also substitute
functions like
Envelope with
ST_Envelope
since 5.7
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
185
Better Performance
mysql (osm) > SELECT ...;
+-----------+------------+------------+-----------------------+
| node_id | longitude | latitude | distance |
+-----------+------------+------------+-----------------------+
| XXXXXXXXX | XXXXXXXXXX | XXXXXXXXXX | 0.0014631428672541478 |
+-----------+------------+------------+-----------------------+
Empty set (0.11 sec)
mysql (osm) > EXPLAIN SELECT ...;
+----+--------+-------+-------+-------+----------+---------+------+---------+------+----------+----------------+
| id | select | table | parti | type | possible | key | key | ref | rows | filtered | Extra |
| | _type | | tions | | _keys | | _len | | | | |
+----+--------+-------+-------+-------+----------+---------+------+---------+------+----------+----------------+
| 1 | SIMPLE | n | NULL | range | PRIMARY | coord | 34 | NULL | 2 | 100.00 | Using where; |
| | | | | | ,coord | | | | | | Using filesort |
| 1 | SIMPLE | n_t1 | NULL | ref | PRIMARY | PRIMARY | 8 | osm.n. | 3 | 1.41 | Using where |
| | | | | | | | | node_id | | | |
| 1 | SIMPLE | n_t2 | NULL | ref | PRIMARY | PRIMARY | 8 | osm.n. | 3 | 1.41 | Using where |
| | | | | | | | | node_id | | | |
+----+--------+-------+-------+-------+----------+---------+------+---------+------+---------------------------+
3 rows in set (0.00 sec)
This field used to
be almost useless
(wait for it)
This field used to
be almost useless
(wait for it)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
186
Better Filtering
Not using the index:
+----------------------------+--------+
| Variable_name | Value |
+----------------------------+--------+
| Handler_commit | 1 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_external_lock | 6 |
| Handler_mrr_init | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 1 |
| Handler_read_key | 1914 |
| Handler_read_last | 0 |
| Handler_read_next | 1954 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 1 |
| Handler_read_rnd_next | 833426 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 0 |
| Handler_write | 1 |
+----------------------------+--------+
18 rows in set (0.00 sec)
mysql-8.0 (osm) > SHOW STATUS LIKE 'Hand%';
Using the BTREE index:
+----------------------------+--------+
| Variable_name | Value |
+----------------------------+--------+
| Handler_commit | 1 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_external_lock | 6 |
| Handler_mrr_init | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 0 |
| Handler_read_key | 274 |
| Handler_read_last | 0 |
| Handler_read_next | 246540 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+--------+
18 rows in set (0.00 sec)
Using the SPATIAL index:
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 1 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_external_lock | 6 |
| Handler_mrr_init | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 0 |
| Handler_read_key | 522 |
| Handler_read_last | 0 |
| Handler_read_next | 5254 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 259 |
| Handler_read_rnd_next | 0 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 0 |
| Handler_write | 0 |
+----------------------------+-------+
18 rows in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
187
Geohash Functions
mysql (osm) > SELECT ST_GeoHash(@lon, @lat, 10);
+----------------------------+
| ST_GeoHash(@lon, @lat, 10) |
+----------------------------+
| u0yjh79gr9 |
+----------------------------+
1 row in set (0.00 sec)
• Useful to index coordinates with a BTREE
– It could be specially useful combined with indexed STORED
columns (emulating quadtrees)
More on Geohashing: http://mysqlserverteam.com/geohash-functions/
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
188
GeoJSON Functions
mysql (osm) > SELECT nm.v, ST_AsGeoJson(n.coord)
FROM node_tags n_t
JOIN nodes n USING (node_id, version)
JOIN node_tags nm USING (node_id, version)
WHERE n_t.k='tourism' AND
n_t.v='attraction' AND
nm.k='name';
+-------------------------+------------------------------------------------------------+
| v | ST_AsGeoJson(n.coord) |
+-------------------------+------------------------------------------------------------+
| Schießbuckel | {"type": "Point", "coordinates": [8.4938556, 49.6351754]} |
| AKW Informationszentrum | {"type": "Point", "coordinates": [8.4175585, 49.7060227]} |
| Burg Gleiberg | {"type": "Point", "coordinates": [8.6344314, 50.6150329]} |
| Brüderkirche | {"type": "Point", "coordinates": [9.5042975, 51.3149351]} |
| Römer | {"type": "Point", "coordinates": [8.6816587, 50.1104684]} |
.
.
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
189
GeoJSON Functions (cont.)
$ mysql osm -B -e "SET NAMES utf8; SET SESSION group_concat_max_len = 10000; SELECT
CONCAT('{"type":"FeatureCollection", "features":[ ',
GROUP_CONCAT(CONCAT('{"type":"Feature", "geometry":', ST_AsGeoJson(n.coord),
',"properties":{"name":"',nm.v,'"}}')), ' ]}') FROM node_tags n_t JOIN nodes n USING
(node_id, version) JOIN node_tags nm USING (node_id, version) WHERE n_t.k='tourism' and
n_t.v='attraction' AND nm.k='name'"
http://geojsonlint.com/
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
190
Older Issues
• Before MySQL 8.0, SRID could be set and retrieved, but all
operations were done in squared euclidean coordinates:
mysql-5.7.5 (osm) > SET @p1 := GeomFromText('POINT(8 50)', 4326);
Query OK, 0 rows affected (0.00 sec)
mysql-5.7.5 (osm) > SET @p2 := GeomFromText('POINT(7 50)', 4326);
Query OK, 0 rows affected (0.00 sec)
mysql-5.7.5 (osm) > SET @p3 := GeomFromText('POINT(8 51)', 4326);
Query OK, 0 rows affected (0.00 sec)
mysql-5.7.5 (osm) > SELECT srid(@p1);
+-----------+
| srid(@p1) |
+-----------+
| 4326 |
+-----------+
1 row in set (0.00 sec)
mysql-5.7.5 (osm) > SELECT st_distance(@p1, @p2);
+-----------------------+
| st_distance(@p1, @p2) |
+-----------------------+
| 1 |
+-----------------------+
1 row in set (0.00 sec)
mysql-5.7.5 (osm) > SELECT st_distance(@p1, @p3);
+-----------------------+
| st_distance(@p1, @p3) |
+-----------------------+
| 1 |
+-----------------------+
1 row in set (0.00 sec)
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
191
New 8.0 SRID Support !
mysql-8.0 (osm) > SET @p1 := ST_GeomFromText('POINT(8 50)',
4326);
Query OK, 0 rows affected (0.00 sec)
mysql-8.0 (osm) > SET @p2 := ST_GeomFromText('POINT(7 50)',
4326);
Query OK, 0 rows affected (0.00 sec)
mysql-8.0 (osm) > SET @p3 := ST_GeomFromText('POINT(8 51)',
4326);
Query OK, 0 rows affected (0.00 sec)
mysql-8.0 (osm) > SELECT ST_srid(@p1);
+--------------+
| ST_srid(@p1) |
+--------------+
| 4326 |
+--------------+
1 row in set (0.00 sec)
mysql-8.0 (osm) > SELECT st_distance(@p1, @p2);
+-----------------------+
| st_distance(@p1, @p2) |
+-----------------------+
| 110243.2689139409 |
+-----------------------+
1 row in set (0.00 sec)
mysql-8.0 (osm) > SELECT st_distance(@p1, @p3);
+-----------------------+
| st_distance(@p1, @p3) |
+-----------------------+
| 110592.12831394037 |
+-----------------------+
1 row in set (0.01 sec)
Answers in
meters- The
world is no
longer flat!
Answers in
meters- The
world is no
longer flat!
I expected
something around
71-72 Km, did I do
something wrong?
I expected
something around
71-72 Km, did I do
something wrong?
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
192
JSON Native Data Type
●
Since 5.7.8, MySQL allows columns defined with
the JSON data type:
mysql-8.0 > CREATE TABLE json_test(id int PRIMARY KEY auto_increment,
content JSON);
Query OK, 0 rows affected (0.03 sec)
mysql-8.0 > INSERT INTO json_test (content) VALUES ('{"type":
"correct_json"}');
Query OK, 1 row affected (0.00 sec)
mysql-8.0 > INSERT INTO json_test (content) VALUES ('{"type":
"incorrect_json}');
ERROR 3140 (22032): Invalid JSON text: "Missing a closing quotation
mark in string." at position 24 in value (or column) '{"type":
"incorrect_json}'.
They get
validated on
insert
They get
validated on
insert
MariaDB
supports it
only as a
BLOB/LONGT
EXT alias
MariaDB
supports it
only as a
BLOB/LONGT
EXT alias
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
193
JSON functions
●
MySQL includes almost all functions to
manipulate JSON that you may think of:
– Validation test: JSON_TYPE
– Object creation: JSON_ARRAY, JSON_MERGE, ...
– Searching: JSON_EXTRACT
– Modifying: JSON_SET, JSON_INSERT, ...
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
194
Indexing JSON
●
JSON Columns cannot be indexed:
mysql [localhost] {msandbox} (test) > ALTER TABLE
json_test ADD INDEX(content);
ERROR 3152 (42000): JSON column 'content' cannot be
used in key specification.
●
However, they can be compared with regular
fields and use indexes thanks to virtual columns
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
195
CONCLUSIONS
Query Optimization with MySQL 8.0 and MariaDB
10.3: The Basics
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
196
8.0/10.3 Recently released
• They are mature enough to be used in production
• Latest release doesn’t have as many new features
related to query optimization
– But they have a lot of other features and quality of
life improvements
– They also fix long outstanding query performance
bugs
– MariaDB also catches up with MySQL in some areas
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
197
MySQL 8.0 New Features
• MySQL 5.6 seemed Percona Server-inspired
• MySQL 5.7 seemed MariaDB-inspired
• MySQL 8.0 seems Galera and Oracle DB-inspired
– Competition is always good for consumer
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
198
Modern SQL is now part of
MySQL!
●
Window functions, recursive CTEs:
WITH RECURSIVE cte (cl_from, cl_type) AS
(
SELECT cl_from, cl_type FROM categorylinks WHERE cl_to = 'Database_management_systems'
-- starting category
UNION
SELECT categorylinks.cl_from, categorylinks.cl_type FROM cte JOIN page ON cl_from =
page_id JOIN categorylinks ON page_title = cl_to WHERE cte.cl_type = 'subcat' --
subcat addition on each iteration
)
SELECT page_title FROM cte JOIN page ON cl_from = page_id WHERE page_namespace = 0
ORDER BY page_title; -- printing only articles in the end, ordered by title
Scan Wikipedia
categories,
page links
recursively!
Scan Wikipedia
categories,
page links
recursively!
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
199
Many Optimizer Advantages Have to
Be Manually Enabled
●
Modifying on a per-query basis:
SET optimizer_switch='batched_key_access=on';
SET join_cache_level=8; # for MariaDB
in order to take advantage of them, making
some of features useless unless you are fine-
tuning
– I expect that to change in the future
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
200
I Herby Declare MyISAM as Dead
• All major MyISAM-only features are
now on MySQL 5.7/8.0
– FULLTEXT
– GIS
– Transportable tables
• Previous blockers such as grant tables (new
InnoDB data dictionary) and temporary tables
are now in InnoDB format
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
201
Benchmarks
●
Do not trust first party
benchmarks
– In fact, do not trust 3rd
party benchmarks either
●
Only care about the
performance of your
application running on your hardware
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
202
Q&A
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
203
Not to Miss
●
Operations track:
TLS for MySQL at Large Scale:
How we do relational data on-
the-wire encryption at the
Wikimedia Foundation
●
Do you want to do query
optimization for a website with
20 Billion views per month?
https://wikimediafoundation.org/about/jobs/
© 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0
Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
204
Thank You for Attending!
●
Do not forget, after the session finishes,
to please login with your Percona Live
app and “Rate This Session”
●
Special thanks to in order by rand() to: Ariel
Glenn, Manuel Arostegui, Morgan Tocker, Sean
Pringle, David Hildebrandt, Bill Karwin, Domas
Mituzas, Mark Callaghan, Shlomi Noach, Valerii
Kravchuk, Miguel Ángel Nieto, Dimitri Kravtchuk,
Olav Sandstå and the whole Wikimedia Team,
and all people at the MariaDB, Percona and
MySQL/Oracle teams, and the Percona Live
Organization and Sponsors

More Related Content

PDF
Replication Troubleshooting in Classic VS GTID
PDF
InnoDB Flushing and Checkpoints
PDF
InnoDB Locking Explained with Stick Figures
PDF
MySQL Parallel Replication: All the 5.7 and 8.0 Details (LOGICAL_CLOCK)
PPTX
Comprehensive Terraform Training
PDF
MySQL fundraising pitch deck ($16 million Series B round - 2003)
PPTX
Artificial Intelligence (AI) for Financial Services
PPTX
INVENTORY MANAGEMENT
Replication Troubleshooting in Classic VS GTID
InnoDB Flushing and Checkpoints
InnoDB Locking Explained with Stick Figures
MySQL Parallel Replication: All the 5.7 and 8.0 Details (LOGICAL_CLOCK)
Comprehensive Terraform Training
MySQL fundraising pitch deck ($16 million Series B round - 2003)
Artificial Intelligence (AI) for Financial Services
INVENTORY MANAGEMENT

What's hot (20)

PDF
Errant GTIDs breaking replication @ Percona Live 2019
PDF
Query Optimization with MySQL 5.7 and MariaDB 10: Even newer tricks
PDF
Best Practice for Achieving High Availability in MariaDB
PDF
OSMC 2022 | VictoriaMetrics: scaling to 100 million metrics per second by Ali...
PDF
ProxySQL and the Tricks Up Its Sleeve - Percona Live 2022.pdf
PDF
Parallel Replication in MySQL and MariaDB
PDF
Deep dive into PostgreSQL statistics.
PDF
ProxySQL - High Performance and HA Proxy for MySQL
PDF
Mvcc in postgreSQL 권건우
PDF
New features in ProxySQL 2.0 (updated to 2.0.9) by Rene Cannao (ProxySQL)
PDF
PostgreSQL : Introduction
PDF
ProxySQL High Avalability and Configuration Management Overview
PDF
Almost Perfect Service Discovery and Failover with ProxySQL and Orchestrator
PPTX
Query logging with proxysql
PDF
Designing microservices platforms with nats
PDF
[2018] MySQL 이중화 진화기
PDF
MySQL Administrator 2021 - 네오클로바
PPTX
ProxySQL for MySQL
PDF
High Availability for OpenStack
PDF
MySQL InnoDB Cluster - New Features in 8.0 Releases - Best Practices
Errant GTIDs breaking replication @ Percona Live 2019
Query Optimization with MySQL 5.7 and MariaDB 10: Even newer tricks
Best Practice for Achieving High Availability in MariaDB
OSMC 2022 | VictoriaMetrics: scaling to 100 million metrics per second by Ali...
ProxySQL and the Tricks Up Its Sleeve - Percona Live 2022.pdf
Parallel Replication in MySQL and MariaDB
Deep dive into PostgreSQL statistics.
ProxySQL - High Performance and HA Proxy for MySQL
Mvcc in postgreSQL 권건우
New features in ProxySQL 2.0 (updated to 2.0.9) by Rene Cannao (ProxySQL)
PostgreSQL : Introduction
ProxySQL High Avalability and Configuration Management Overview
Almost Perfect Service Discovery and Failover with ProxySQL and Orchestrator
Query logging with proxysql
Designing microservices platforms with nats
[2018] MySQL 이중화 진화기
MySQL Administrator 2021 - 네오클로바
ProxySQL for MySQL
High Availability for OpenStack
MySQL InnoDB Cluster - New Features in 8.0 Releases - Best Practices
Ad

Similar to Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics (20)

PPTX
Zendcon scaling magento
PDF
6 tips for improving ruby performance
ODP
Caching and tuning fun for high scalability
PPTX
Lonestar php scalingmagento
PDF
Time series denver an introduction to prometheus
ODP
Caching and tuning fun for high scalability @ FOSDEM 2012
PDF
Macy's: Changing Engines in Mid-Flight
PPTX
millions-gc-jax-2022.pptx
PDF
MagentoECG-UsingRedisasaCacheBackendinMagento
PDF
MySQL Scalability and Reliability for Replicated Environment
PDF
How to reduce expenses on monitoring
PDF
Bringing the JAMstack to the Enterprise
PDF
Stream Processing in SmartNews #jawsdays
PPTX
1404 app dev series - session 8 - monitoring & performance tuning
PDF
stackconf 2023 | How to reduce expenses on monitoring with VictoriaMetrics by...
PPTX
Enterprise application performance - Understanding & Learnings
PDF
Performance schema in_my_sql_5.6_pluk2013
PDF
MySQL Transformation Case Study: 80% Cost Savings & Uninterrupted Availabilit...
PPTX
this-is-garbage-talk-2022.pptx
PPTX
Let the Tiger Roar! - MongoDB 3.0 + WiredTiger
Zendcon scaling magento
6 tips for improving ruby performance
Caching and tuning fun for high scalability
Lonestar php scalingmagento
Time series denver an introduction to prometheus
Caching and tuning fun for high scalability @ FOSDEM 2012
Macy's: Changing Engines in Mid-Flight
millions-gc-jax-2022.pptx
MagentoECG-UsingRedisasaCacheBackendinMagento
MySQL Scalability and Reliability for Replicated Environment
How to reduce expenses on monitoring
Bringing the JAMstack to the Enterprise
Stream Processing in SmartNews #jawsdays
1404 app dev series - session 8 - monitoring & performance tuning
stackconf 2023 | How to reduce expenses on monitoring with VictoriaMetrics by...
Enterprise application performance - Understanding & Learnings
Performance schema in_my_sql_5.6_pluk2013
MySQL Transformation Case Study: 80% Cost Savings & Uninterrupted Availabilit...
this-is-garbage-talk-2022.pptx
Let the Tiger Roar! - MongoDB 3.0 + WiredTiger
Ad

More from Jaime Crespo (12)

PDF
Haciendo copias de seguridad de todo el conocimiento humano con python y soft...
PDF
Backing up Wikipedia Databases
PDF
Wikipedia Burgos devfest 2017
PDF
Scaling and hardware provisioning for databases (lessons learned at wikipedia)
PDF
MySQL Schema Design in Practice
PDF
MySQL at Wikipedia: How we do relational data at the Wikimedia Foundation
PDF
Query optimization: from 0 to 10 (and up to 5.7)
PDF
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
ODP
Why MySQL
ODP
MySQL GUI Administration
PDF
Software libre para gestión de proyectos
PDF
warptalk: Experiencia de usuario y usabilidad
Haciendo copias de seguridad de todo el conocimiento humano con python y soft...
Backing up Wikipedia Databases
Wikipedia Burgos devfest 2017
Scaling and hardware provisioning for databases (lessons learned at wikipedia)
MySQL Schema Design in Practice
MySQL at Wikipedia: How we do relational data at the Wikimedia Foundation
Query optimization: from 0 to 10 (and up to 5.7)
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
Why MySQL
MySQL GUI Administration
Software libre para gestión de proyectos
warptalk: Experiencia de usuario y usabilidad

Recently uploaded (20)

PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PDF
medical staffing services at VALiNTRY
PPTX
Introduction to Artificial Intelligence
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PPTX
history of c programming in notes for students .pptx
PDF
Adobe Illustrator 28.6 Crack My Vision of Vector Design
PDF
2025 Textile ERP Trends: SAP, Odoo & Oracle
PPTX
Transform Your Business with a Software ERP System
PPTX
Reimagine Home Health with the Power of Agentic AI​
PDF
Nekopoi APK 2025 free lastest update
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PPTX
ai tools demonstartion for schools and inter college
PDF
System and Network Administration Chapter 2
PDF
AI in Product Development-omnex systems
PDF
System and Network Administraation Chapter 3
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PPTX
L1 - Introduction to python Backend.pptx
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
medical staffing services at VALiNTRY
Introduction to Artificial Intelligence
Internet Downloader Manager (IDM) Crack 6.42 Build 41
wealthsignaloriginal-com-DS-text-... (1).pdf
history of c programming in notes for students .pptx
Adobe Illustrator 28.6 Crack My Vision of Vector Design
2025 Textile ERP Trends: SAP, Odoo & Oracle
Transform Your Business with a Software ERP System
Reimagine Home Health with the Power of Agentic AI​
Nekopoi APK 2025 free lastest update
Upgrade and Innovation Strategies for SAP ERP Customers
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
ai tools demonstartion for schools and inter college
System and Network Administration Chapter 2
AI in Product Development-omnex systems
System and Network Administraation Chapter 3
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
L1 - Introduction to python Backend.pptx

Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics

  • 1. Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization With MySQL 8.0 and MariaDB 10.3: The Basics Jaime Crespo Percona Live Europe 2018 -Frankfurt, 5 Nov 2018- dbahire.com/pleu18
  • 2. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 2 Agenda 1. Introduction 6. Joins 2. Query Profiling 7. Subqueries 3. Access Types and Basic Indexing Techniques 8. FULLTEXT, Geodata Search and JSON support 4. Multi-Column Indexing 9. Conclusions 5. Break
  • 3. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 3 INTRODUCTION Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
  • 4. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 4 This is me fighting bad query performance ● Sr. Database Administrator at Wikimedia Foundation ● Used to work as a trainer for Oracle (MySQL), as a Consultant (Percona) and as a Freelance administrator (DBAHire.com)
  • 5. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 5 Example Application (I) • Wiktionary (and all Wikimedia projects’ data) is licensed under the Creative Commons BY-SA-3.0 License and is Copyright its Contributors
  • 6. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 6 Example Application (II) • OSM Database is licensed under the Open DataBase License and is Copyright OpenStreetMap Contributors
  • 7. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 7 Install the example databases ● Downloads and instructions at: http://dbahire.com/pleu18 – Requirements: a MySQL 8.0 or MariaDB 10.3 installation – The Wiktionary and OSM extracts ● Import them by doing: $ tar xf <file> && cd <dir> && myloader -d .
  • 8. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 8 Alternative: Query the Live Database ● https://quarry.wmflabs.org/
  • 9. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 9 QUERY PROFILING Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
  • 10. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 10 Which Queries Should I Optimize First? ● Tools: – Application-level profiling – General monitoring metrics (CPU, IO, etc.) – Aggregated PROCESSLIST – pt-query-digest – PERFORMANCE_SCHEMA ● They are not exclusive to each other
  • 11. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 11 Application-Level profiling
  • 12. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 12 General Monitoring Metrics New index added at 15:30h
  • 13. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 13 pt-query-digest • It is a 3rd party tool written in Perl, originally created by Baron Schwartz • It requires activation of the slow log: – SET GLOBAL slow_query_log = 1; – SET long_query_time = 0; • In Percona Server and MariaDB it can provide extra information: – SHOW GLOBAL VARIABLES like 'log_slow_verbosity'; Be careful with extra IO and latency! Be careful with extra IO and latency!
  • 14. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 14 pt-query-digest Execution (I) # 1094.7s user time, 9.4s system time, 141.22M rss, 205.21M vsz # Current date: Wed Jul 1 07:32:28 2015 # Hostname: db1018 # Files: STDIN # Overall: 4.66M total, 640 unique, 53.47 QPS, 0.02x concurrency _________ # Time range: 2015-06-30 07:00:10 to 2015-07-01 07:11:37 # Attribute total min max avg 95% stddev median # ============ ======= ======= ======= ======= ======= ======= ======= # Exec time 1320s 1us 3s 283us 332us 3ms 152us # Lock time 238s 0 13ms 51us 93us 39us 52us # Rows sent 5.02M 0 4.16k 1.13 1.96 8.69 0.99 # Rows examine 9.50M 0 232.93k 2.14 3.89 261.15 0.99 # Merge passes 0 0 0 0 0 0 0 # Query size 1.06G 17 67.89k 243.89 511.45 368.99 192.76 # Boolean: # Filesort 8% yes, 91% no # Full scan 94% yes, 5% no # Priority que 3% yes, 96% no # Tmp table 29% yes, 70% no # Tmp table on 1% yes, 98% no Actual execution on Wikipedia production servers Actual execution on Wikipedia production servers
  • 15. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 15 pt-query-digest Execution (II) # Profile # Rank Query ID Response time Calls R/Call V/M Item # ==== ================== ============== ======= ====== ===== ============ # 1 0xSANITIZED 242.2765 18.4% 691005 0.0004 0.00 SELECT revision page user # 2 0xSANITIZED 204.7052 15.5% 80863 0.0025 0.01 SELECT revision page user # 3 0xSANITIZED 162.8476 12.3% 1025179 0.0002 0.00 SELECT page # 4 0xSANITIZED 68.1164 5.2% 93928 0.0007 0.01 SELECT revision page user # 5 0xSANITIZED 66.8302 5.1% 354562 0.0002 0.00 SELECT page revision # 6 0xSANITIZED 57.0374 4.3% 211631 0.0003 0.00 SELECT page revision # 7 0xSANITIZED 44.0751 3.3% 6925 0.0064 0.07 SELECT page categorylinks category # 8 0xSANITIZED 35.0655 2.7% 9689 0.0036 0.00 SELECT text # 9 0xSANITIZED 29.4363 2.2% 152259 0.0002 0.00 SELECT page # 10 0xSANITIZED 24.1864 1.8% 176927 0.0001 0.00 SELECT msg_resource # 11 0xSANITIZED 23.7016 1.8% 144807 0.0002 0.00 SELECT page_restrictions # 12 0xSANITIZED 16.6547 1.3% 10135 0.0016 0.03 SELECT revision # 13 0xSANITIZED 15.0564 1.1% 263809 0.0001 0.00 SET
  • 16. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 16 pt-query-digest Execution (III) # Query 1: 7.93 QPS, 0.00x concurrency, ID 0xSANITIZED at byte 1553864032 # This item is included in the report because it matches –limit. # Scores: V/M = 0.00 # Time range: 2015-06-30 07:00:10 to 2015-07-01 07:11:37 # Attribute pct total min max avg 95% stddev median # ============ === ======= ======= ======= ======= ======= ======= ======= # Count 14 691005 # Exec time 18 242s 163us 91ms 350us 348us 563us 301us # Lock time 26 63s 47us 7ms 91us 103us 14us 84us # Rows sent 12 657.18k 0 1 0.97 0.99 0.16 0.99 # Rows examine 6 657.18k 0 1 0.97 0.99 0.16 0.99 # Query size 31 345.42M 501 749 524.16 537.02 9.22 511.45 # String: # Databases itwiki (225976/32%), enwiktiona... (219461/31%)... 15 more # Hosts # Users wikiuser # Query_time distribution # 1us # 10us # 100us ################################################################ # 1ms # # 10ms # # 100ms # 1s # 10s+ # Tables # SHOW TABLE STATUS FROM `enwiktionary` LIKE 'revision'G # SHOW CREATE TABLE `enwiktionary`.`revision`G # SHOW TABLE STATUS FROM `enwiktionary` LIKE 'page'G # SHOW CREATE TABLE `enwiktionary`.`page`G # SHOW TABLE STATUS FROM `enwiktionary` LIKE 'user'G # SHOW CREATE TABLE `enwiktionary`.`user`G # EXPLAIN /*!50100 PARTITIONS*/ SELECT /* Revision::fetchFromConds SANITIZED */ * FROM `revision` INNER JOIN `page` ON ((page_id = rev_page)) LEFT JOIN `user` ON ((rev_user != 0) AND (user_id = rev_user)) WHERE page_namespace = '0' AND page_title = 'SANITIZED' AND (rev_id=page_latest) LIMIT 1G
  • 17. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 17 PERFORMANCE_SCHEMA • Monitoring schema (engine) enabled by default since MySQL 5.6 – performance_schema = 1 (it is not dynamic) • Deprecates the old query profiling • It is way more user-friendly when combined with the SYS schema/ps_helper (a set of views and stored procedures created by Mark Leith) – Included by default since 5.7.7
  • 18. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 18 Installation of the SYS Schema <5.7/MariaDB $ git clone https://github.com/MarkLeith/mysql-sys.git Cloning into 'mysql-sys'... remote: Counting objects: 926, done. remote: Compressing objects: 100% (73/73), done. remote: Total 926 (delta 35), reused 6 (delta 2) Receiving objects: 100% (926/926), 452.19 KiB | 225.00 KiB/s, done. Resolving deltas: 100% (584/584), done. $ cd mysql-sys/ $ mysql < sys_56.sql
  • 19. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 19 Example Usage: Discovering Unused Indexes mysql (osm) > SELECT * FROM sys.schema_unused_indexes LIMIT 5; +---------------+-------------------+---------------------------------+ | object_schema | object_name | index_name | +---------------+-------------------+---------------------------------+ | osm | acls | acls_k_idx | | osm | changeset_tags | changeset_tags_id_idx | | osm | current_nodes | current_nodes_timestamp_idx | | osm | current_nodes | current_nodes_tile_idx | | osm | current_relations | current_relations_timestamp_idx | +---------------+-------------------+---------------------------------+ 5 rows in set (0.04 sec) mysql-5.7.8 (osm) > SELECT * FROM current_nodes WHERE tile = 100; ... mysql-5.7.8 (osm) > SELECT * FROM sys.schema_unused_indexes LIMIT 5; +---------------+-------------------+---------------------------------+ | object_schema | object_name | index_name | +---------------+-------------------+---------------------------------+ | osm | acls | acls_k_idx | | osm | changeset_tags | changeset_tags_id_idx | | osm | current_nodes | current_nodes_timestamp_idx | | osm | current_relations | current_relations_timestamp_idx | | osm | current_relations | changeset_id | +---------------+-------------------+---------------------------------+ 5 rows in set (0.03 sec) With enough activity, it can help us clean up our schema With enough activity, it can help us clean up our schema
  • 20. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 20 Example Usage: Slow Queries (ordered by server time) *************** 1. row *************** query: SELECT `way_id` AS `id` , `v` FROM `way_tags` WHERE `v` LIKE ? db: osm full_scan: * exec_count: 15 err_count: 0 warn_count: 0 total_latency: 7.83 s max_latency: 1.33 s avg_latency: 521.84 ms lock_latency: 17.94 ms rows_sent: 6779 rows_sent_avg: 452 mysql (osm) > SELECT * FROM sys.statement_analysis LIMIT 10G rows_examined: 20152155 rows_examined_avg: 1343477 rows_affected: 0 rows_affected_avg: 0 tmp_tables: 0 tmp_disk_tables: 0 rows_sorted: 0 sort_merge_passes: 0 digest: 21f90695b1ebf20a5f4d4c1e5e860f58 first_seen: 2014-11-01 17:04:51 last_seen: 2014-11-01 17:05:22
  • 21. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 21 Example Usage: Top Queries Creating Temporary Tables mysql (osm) > SELECT * FROM sys.statements_with_temp_tables WHERE db = 'osm' LIMIT 10G *************************** 1. row *************************** query: SELECT ? AS TYPE , `node_id` A ... gs` WHERE `k` = ? AND `v` = ? db: osm exec_count: 11 total_latency: 7.57 s memory_tmp_tables: 11 disk_tmp_tables: 0 avg_tmp_tables_per_query: 1 tmp_tables_to_disk_pct: 0 first_seen: 2014-11-01 17:33:55 last_seen: 2014-11-01 17:34:45 digest: 5e6e82799b7c7c0e5c57cfe63eb98d5d
  • 22. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 22 Example Usage: Top Queries Creating Temporary Tables (cont.) mysql (osm) > SELECT DIGEST_TEXT FROM performance_schema.events_statements_summary_by_digest WHERE digest = '5e6e82799b7c7c0e5c57cfe63eb98d5d'G *************************** 1. row *************************** DIGEST_TEXT: SELECT ? AS TYPE , `node_id` AS `id` FROM `node_tags` WHERE `k` = ? AND `v` = ? UNION SELECT ? AS TYPE , `way_id` AS `id` FROM `way_tags` WHERE `k` = ? AND `v` = ? UNION SELECT ? AS TYPE , `relation_id` AS `id` FROM `relation_tags` WHERE `k` = ? AND `v` = ? 1 row in set (0.00 sec) mysql-5.7.8 (osm) > EXPLAIN SELECT 'node' as type, node_id as id FROM node_tags WHERE k='amenity' and v='cafe' UNION SELECT 'way' as type, way_id as id FROM way_tags WHERE k='amenity' and v='cafe' UNION SELECT 'relation' as type, relation_id as id FROM relation_tags WHERE k='amenity' and v='cafe'; +----+--------------+---------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+---------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+ | 1 | PRIMARY | node_tags | NULL | ALL | NULL | NULL | NULL | NULL | 851339 | 0.00 | Using where | | 2 | UNION | way_tags | NULL | ALL | NULL | NULL | NULL | NULL | 1331016 | 0.00 | Using where | | 3 | UNION | relation_tags | NULL | ALL | NULL | NULL | NULL | NULL | 63201 | 0.00 | Using where | | NULL | UNION RESULT | <union1,2,3> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary | +----+--------------+---------------+------------+------+---------------+------+---------+------+---------+----------+-----------------+ 4 rows in set, 1 warning (0.01 sec)
  • 23. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 23 ACCESS TYPES AND BASIC INDEXING TECHNIQUES Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
  • 24. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 24 EXPLAIN ● Essential to understand the execution plan of our queries – Works on SELECTs, INSERTs, UPDATEs, REPLACEs, DELETEs and connections – Fully documented on: https://dev.mysql.com/doc/refman/8.0/en/expl ain-output.html
  • 25. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 25 EXPLAIN Example MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'; +------+-------------+-------+------+--------------- +------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+-------+------+--------------- +------+---------+------+--------+-------------+ | 1 | SIMPLE | page | ALL | NULL | NULL | NULL | NULL | 778885 | Using where | +------+-------------+-------+------+--------------- +------+---------+------+--------+-------------+ 1 row in set (0.00 sec) Difficult to see something Difficult to see something
  • 26. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 26 EXPLAIN Example (vertical format) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 778885 Extra: Using where 1 row in set (0.00 sec) Use G for vertical formatting Use G for vertical formatting
  • 27. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 27 EXPLAIN Example (id) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 778885 Extra: Using where 1 row in set (0.00 sec) Indicates nesting level, not execution order Indicates nesting level, not execution order
  • 28. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 28 EXPLAIN Example (select_type) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 778885 Extra: Using where 1 row in set (0.00 sec) Not a subquery or a UNION Not a subquery or a UNION
  • 29. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 29 EXPLAIN Example (table) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 778885 Extra: Using where 1 row in set (0.00 sec) Table scanned for this step Table scanned for this step
  • 30. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 30 EXPLAIN Example (type) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 778885 Extra: Using where 1 row in set (0.00 sec) All rows are read for this table (FULL TABLE SCAN) All rows are read for this table (FULL TABLE SCAN)
  • 31. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 31 EXPLAIN Example (rows) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 778885 Extra: Using where 1 row in set (0.00 sec) Estimated number of rows to be read (all table rows) Estimated number of rows to be read (all table rows)
  • 32. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 32 How to improve performance? ● Let's add an index on page.page_title: MariaDB [dewiktionary]> ALTER TABLE page ADD INDEX page_title (page_title); Query OK, 0 rows affected (22.70 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> SELECT * FROM page WHERE page_title = 'German'; 1 row in set (0.10 sec)
  • 33. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 33 Index creation results (type) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ref possible_keys: page_title key: page_title key_len: 257 ref: const rows: 1 Extra: Using index condition 1 row in set (0.00 sec) type: ref means that an equality comparison will be checked against an index and several results could be returned type: ref means that an equality comparison will be checked against an index and several results could be returned
  • 34. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 34 Index creation results (possible_keys and key) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ref possible_keys: page_title key: page_title key_len: 257 ref: const rows: 1 Extra: Using index condition 1 row in set (0.00 sec) index(es) that the optimizer considered potentially useful, and final index chosen index(es) that the optimizer considered potentially useful, and final index chosen
  • 35. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 35 Index creation results (ref) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ref possible_keys: page_title key: page_title key_len: 257 ref: const rows: 1 Extra: Using index condition 1 row in set (0.00 sec) Index is compared with a constant, not with another table Index is compared with a constant, not with another table
  • 36. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 36 Index creation results (rows) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title = 'German'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ref possible_keys: page_title key: page_title key_len: 257 ref: const rows: 1 Extra: Using index condition 1 row in set (0.00 sec) Only 1 row read. In this case, estimation is exact (thanks to index dive) Only 1 row read. In this case, estimation is exact (thanks to index dive)
  • 37. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 37 Index creation results (query time) MariaDB [dewiktionary]> SELECT * FROM page WHERE page_title = 'German'; 1 row in set (0.00 sec) Query time has been reduced substantially Query time has been reduced substantially
  • 38. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 38 Types of indexes ● BTREE – B-TREE in MyISAM, B+TREE in InnoDB ● HASH – Only available for MEMORY and NDB ● FULLTEXT – Inverted indexes in MyISAM and InnoDB ● SPATIAL – RTREEs in MyISAM and InnoDB
  • 39. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 39 Finding “German” with a BTREE GoalGoal NastyNasty BossBoss EtchEtch LeekLeek SchoolSchool AvastAvast GolfGolf LandscapeLandscape RoverRover GermanGerman HarlemHarlem MaelstromMaelstrom WalrusWalrus TABLETABLE IcebergIceberg
  • 40. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 40 Finding “German” with a BTREE AvastAvast GolfGolf LandscapeLandscape RoverRover GermanGerman HarlemHarlem MaelstromMaelstrom WalrusWalrus TABLETABLE GoalGoal NastyNasty BossBoss EtchEtch LeekLeek SchoolSchool IcebergIceberg
  • 41. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 41 Do indexes always work? (1/2) ● Can we use an index to make this query faster? SELECT * FROM page WHERE page_title like 'Spa%';
  • 42. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 42 It is a range MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title like 'Spa%'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: range possible_keys: page_title key: page_title key_len: 257 ref: NULL rows: 563 Extra: Using index condition 1 row in set (0.00 sec) Despite not being an equality, we can use the index to find the values quickly Despite not being an equality, we can use the index to find the values quickly
  • 43. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 43 EtchEtch BTREE Indexes can be used for ranges, too AvastAvast GolfGolf LandscapeLandscape RoverRover GermanGerman HarlemHarlem MaelstromMaelstrom WalrusWalrus TABLETABLE GoalGoal NastyNasty BossBoss LeekLeek SchoolSchool IcebergIceberg
  • 44. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 44 Do indexes always work? (2/2) ● What about this other query? SELECT * FROM page WHERE page_title like '%erman';
  • 45. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 45 Let's check with EXPLAIN MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title like '%erman'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 778885 Extra: Using where 1 row in set (0.00 sec) No index can be used for filtering. A full table scan is performed. No index can be used for filtering. A full table scan is performed.
  • 46. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 46 BTREE Index AvastAvast GolfGolf LandscapeLandscape RoverRover GermanGerman HarlemHarlem MaelstromMaelstrom WalrusWalrus TABLETABLE ? GoalGoal NastyNasty BossBoss EtchEtch LeekLeek SchoolSchool IcebergIceberg
  • 47. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 47 Btree indexes usage (I) ● Filtering (“WHERE”) – Equality (operator '=') – Ranges (BETWEEN … AND, >, <, >=, <=, like 'prefix%') – “EXISTS” operators: IN, OR on the same column ● Ordering – ORDER BY (indexed columns) – GROUP BY (indexed columns)
  • 48. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 48 Btree indexes usage (II) ● Returning values directly from the index – Covering index – Functions like max(), min(), etc. ● Reduced contention (writes) – Locking less rows, increasing concurrency
  • 49. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 49 type: const MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_id = 2G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: const possible_keys: PRIMARY key: PRIMARY key_len: 4 ref: const rows: 1 Extra: 1 row in set (0.00 sec) 'const' is a special case of 'ref', when the index can assure that only 1 result can be returned (equality + primary key or unique key). It is faster. 'const' is a special case of 'ref', when the index can assure that only 1 result can be returned (equality + primary key or unique key). It is faster.
  • 50. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 50 type: NULL MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_id = -1G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: NULL type: NULL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: NULL Extra: Impossible WHERE noticed after reading const tables 1 row in set (0.00 sec) 'NULL' is not really a plan, just an optimization that allows immediately discarding impossible conditions 'NULL' is not really a plan, just an optimization that allows immediately discarding impossible conditions
  • 51. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 51 type: ref_or_null MariaDB [osm]> EXPLAIN SELECT * FROM nodes WHERE tile = 1 or tile is nullG *************************** 1. row *************************** id: 1 select_type: SIMPLE table: nodes type: ref_or_null possible_keys: nodes_tile_idx key: nodes_tile_idx key_len: 5 ref: const rows: 2 Extra: Using index condition; Using where 1 row in set (0.00 sec) Equivalent to 'ref', but also takes into account NULL values Equivalent to 'ref', but also takes into account NULL values
  • 52. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 52 type: range (using IN / OR) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_title IN ('German', 'English', 'Spanish')G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: range possible_keys: page_title key: page_title key_len: 257 ref: NULL rows: 4 Extra: Using index condition 1 row in set (0.00 sec) Despite being a range, its execution is very different from ranges using like, between or inequality operators Despite being a range, its execution is very different from ranges using like, between or inequality operators
  • 53. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 53 Is this a bug? (1/2) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_namespace > 200G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ref possible_keys: name_title key: name_title key_len: 4 ref: const rows: 50 Extra: 1 row in set (0.00 sec) An index is used to return pages with namespace>200 An index is used to return pages with namespace>200
  • 54. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 54 Is this a bug? (2/2) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_namespace < 200G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ALL possible_keys: name_title key: NULL key_len: NULL ref: NULL rows: 778885 Extra: Using where 1 row in set (0.00 sec) The index is not used with ns=0 The index is not used with ns=0
  • 55. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 55 Using an index is sometimes suboptimal This point is not fixed, and depends on the hardware and buffer state This point is not fixed, and depends on the hardware and buffer state
  • 56. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 56 What index should we add to make this query faster? MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE left(rev_timestamp, 6) = '201809'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 1376651 Extra: Using where 1 row in set (0.00 sec)
  • 57. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 57 The table has already an index on rev_timestamp MariaDB [dewiktionary]> SHOW CREATE TABLE revisionG *************************** 1. row *************************** Table: revision Create Table: CREATE TABLE `revision` ( ... KEY `rev_timestamp` (`rev_timestamp`),
  • 58. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 58 We need to rewrite the query MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_timestamp >= '201509' and rev_timestamp < '201510'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: rev_timestamp key: rev_timestamp key_len: 14 ref: NULL rows: 24588 Extra: Using index condition 1 row in set (0.00 sec)
  • 59. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 59 This transformation is not trivial or even possible in all cases MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE substr(rev_timestamp, 5, 2) = '09'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL Rows: 1376651 Extra: Using where 1 row in set (0.00 sec) Can you think of a way to improve this query? Can you think of a way to improve this query?
  • 60. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 60 Indexes for Ordering MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_touched DESC LIMIT 10G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 778885 Extra: Using filesort 1 row in set (0.00 sec) "Using filesort" indicates that an ordering is needed before returning the results "Using filesort" indicates that an ordering is needed before returning the results
  • 61. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 61 If that is frequent, we can create an index on page_touched...[dewiktionary]> ALTER TABLE page ADD INDEX page_page_touched(page_touched); Query OK, 0 rows affected (3.03 sec) Records: 0 Duplicates: 0 Warnings: 0 [dewiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_touched DESC LIMIT 10G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: index possible_keys: NULL key: page_page_touched key_len: 16 ref: NULL rows: 10 Extra: 1 row in set (0.00 sec) The index does not produce any advantage for filtering The index does not produce any advantage for filtering However, it is very effective by helping to avoid the sort phase However, it is very effective by helping to avoid the sort phase
  • 62. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 62 It can return data in index order faster AvastAvast GolfGolf LandscapeLandscape RoverRover GermanGerman HarlemHarlem MaelstromMaelstrom WalrusWalrus TABLETABLE GoalGoal NastyNasty BossBoss EtchEtch LeekLeek SchoolSchool IcebergIceberg
  • 63. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 63 Trick: ORDER BY NULL avoids filesortMariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision GROUP BY substr(rev_timestamp, 5, 2) = '09'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 1376651 Extra: Using temporary; Using filesort 1 row in set (0.00 sec) MariaDB [nlwiktionary]> EXPLAIN SELECT * FROM revision GROUP BY substr(rev_timestamp, 5, 2) = '09' ORDER BY NULLG *************************** 1. row *************************** ... rows: 1376651 Extra: Using temporary 1 row in set (0.00 sec) The advantage is not too big, but it avoids the filesort The advantage is not too big, but it avoids the filesort There is no good index in this case There is no good index in this case
  • 64. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 64 Does LIMIT improve the performance? (I) MariaDB [dewiktionary]> ALTER TABLE page ADD INDEX page_touched (page_touched); […] MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_touchedG *************************** 1. row *************************** type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 783231 Extra: Using filesort MariaDB [dewiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_touched LIMIT 10G *************************** 1. row *************************** type: index possible_keys: NULL key: page_page_touched key_len: 16 ref: NULL rows: 10 Extra: 1 row in set (0.00 sec) In some cases it can be essential to allow effective usage of the indexes In some cases it can be essential to allow effective usage of the indexes
  • 65. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 65 Does LIMIT improve the performance? (II)MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision ORDER BY rev_commentG *************************** 1. row *************************** [...] type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 817636 Extra: Using filesort 1 row in set (0.00 sec) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision ORDER BY rev_comment LIMIT 10G *************************** 1. row *************************** […] table: revision type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 817636 Extra: Using filesort 1 row in set (0.00 sec) In other cases, it has no effect on the scanned rows (just on the returned ones) In other cases, it has no effect on the scanned rows (just on the returned ones)
  • 66. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 66 Does LIMIT improve the performance? (III)MariaDB [nlwiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_title LIMIT 100G *************************** 1. row *************************** type: index possible_keys: NULL key: page_title key_len: 257 ref: NULL rows: 100 Extra: 1 row in set (0.00 sec) MariaDB [nlwiktionary]> EXPLAIN SELECT * FROM page ORDER BY page_title LIMIT 10000, 100G *************************** 1. row *************************** type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 90956 Extra: Using filesort 1 row in set (0.00 sec) In this case, performance will vary depending on the offset (not ideal) In this case, performance will vary depending on the offset (not ideal)
  • 67. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 67 Indexes and GROUP BY (no indexes) MariaDB [dewiktionary]> EXPLAIN SELECT rev_page, count(*) FROM revision IGNORE INDEX(rev_page_id, page_timestamp, page_user_timestamp) GROUP BY rev_pageG *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 1376651 Extra: Using temporary; Using filesort 1 row in set (0.00 sec) Without indexes, a temporary table is created to order results Without indexes, a temporary table is created to order results
  • 68. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 68 Indexes and GROUP BY (rev_page_id) MariaDB [dewiktionary]> EXPLAIN SELECT rev_page, count(*) FROM revision GROUP BY rev_pageG *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: index possible_keys: NULL key: rev_page_id key_len: 8 ref: NULL rows: 1376651 Extra: Using index 1 row in set (0.00 sec) The index does not produce any advantage for filtering (there is no WHERE clause) The index does not produce any advantage for filtering (there is no WHERE clause) However, thanks to it we avoid a sort and a temporary table However, thanks to it we avoid a sort and a temporary table
  • 69. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 69 type: index, loose index scan and covering index (1/3) node_id version k v 234234344545 1 leaves deciduous 234234344546 3 name Northolt Road 234234344548 5 gps:latitude 490018321N 234234344549 6 access uncontrolled 234234344550 1 editor JOSM 234234344551 9 name Big Ben 234234344552 1 source survey 234234344557 1 name London Plane CarlsbergCarlsberg recyclingrecycling Accurate as of 2010 Accurate as of 2010 Gwynne Place Gwynne Place Manor PlaceManor Place treetree 490018321N490018321N deciduousdeciduous London PlaneLondon Plane surveysurvey Big BenBig Ben JOSMJOSM Northolt RoadNortholt Road uncontrolleduncontrolled London Borough of Southwark London Borough of Southwark With 'type:index', all rows are read in index order (full index scan) With 'type:index', all rows are read in index order (full index scan)
  • 70. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 70 type: index, loose index scan and covering index (2/3) node_id version k v 234234344545 1 leaves deciduous 234234344546 3 name Northolt Road 234234344548 5 gps:latitude 490018321N 234234344549 6 access uncontrolled 234234344550 1 editor JOSM 234234344551 9 name Big Ben 234234344552 1 source survey 234234344557 1 name London Plane CarlsbergCarlsberg recyclingrecycling Accurate as of 2010 Accurate as of 2010 Gwynne Place Gwynne Place Manor PlaceManor Place treetree 490018321N490018321N deciduousdeciduous London PlaneLondon Plane surveysurvey Big BenBig Ben JOSMJOSM Northolt RoadNortholt Road uncontrolleduncontrolled London Borough of Southwark London Borough of Southwark If we have in addition 'Using index for group-by' we have the loose index scan optimization If we have in addition 'Using index for group-by' we have the loose index scan optimization
  • 71. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 71 Loose Index Scan Example MariaDB [dewiktionary]> EXPLAIN SELECT rev_page, max(rev_timestamp) FROM revision GROUP BY rev_pageG *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: NULL key: page_timestamp key_len: 4 ref: NULL rows: 80980 Extra: Using index for group-by 1 row in set (0.00 sec)
  • 72. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 72 type: index, loose index scan and covering index (3/3) CarlsbergCarlsberg recyclingrecycling Accurate as of 2010 Accurate as of 2010 Gwynne Place Gwynne Place Manor PlaceManor Place treetree 490018321N490018321N deciduousdeciduous London PlaneLondon Plane surveysurvey Big BenBig Ben JOSMJOSM Northolt RoadNortholt Road uncontrolleduncontrolled London Borough of Southwark London Borough of Southwark If we have in addition 'Using index' we have the covering index optimization If we have in addition 'Using index' we have the covering index optimization node_id version k v 234234344545 1 leaves deciduous 234234344546 3 name Northolt Road 234234344548 5 gps:latitude 490018321N 234234344549 6 access uncontrolled 234234344550 1 editor JOSM 234234344551 9 name Big Ben 234234344552 1 source survey 234234344557 1 name London Plane
  • 73. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 73 Covering Index Example (1/3) MariaDB [dewiktionary]> ALTER TABLE revision DROP INDEX rev_page_id, drop index page_timestamp, drop index page_user_timestamp; Query OK, 0 rows affected (0.04 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> EXPLAIN SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page = 31579G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 1376651 Extra: Using where 1 row in set (0.00 sec) MariaDB [dewiktionary]> SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page = 31579G *************************** 1. row *************************** count(DISTINCT rev_user): 1 1 row in set (3.57 sec) Let's start with no indexesLet's start with no indexes
  • 74. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 74 Covering Index Example (2/3) MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX revision_rev_page(rev_page); Query OK, 0 rows affected (12.38 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> EXPLAIN SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page = 31579G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ref possible_keys: revision_rev_page key: revision_rev_page key_len: 4 ref: const rows: 4038 Extra: 1 row in set (0.00 sec) MariaDB [dewiktionary]> SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page = 31579G *************************** 1. row *************************** count(DISTINCT rev_user): 1 1 row in set (0.04 sec) Adding an index on rev_page increases the speed due to improved filtering Adding an index on rev_page increases the speed due to improved filtering
  • 75. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 75 Covering Index Example (3/3) MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX revision_rev_page_rev_user(rev_page, rev_user); Query OK, 0 rows affected (13.20 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> EXPLAIN SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page = 31579 G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ref possible_keys: revision_rev_page,revision_rev_page_rev_user key: revision_rev_page_rev_user key_len: 4 ref: const rows: 4038 Extra: Using index 1 row in set (0.00 sec) MariaDB [dewiktionary]> SELECT count(DISTINCT rev_user) FROM revision WHERE rev_page = 31579G *************************** 1. row *************************** count(DISTINCT rev_user): 1 1 row in set (0.00 sec) rev_page, rev_user does not increase the index selectiveness, but allows returning results directly from the index rev_page, rev_user does not increase the index selectiveness, but allows returning results directly from the index The speed difference can be hugeThe speed difference can be huge
  • 76. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 76 MULTI-COLUMN INDEXES Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
  • 77. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 77 In many cases, conditions are applied on more than one column MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page = 31579 and rev_timestamp < '2008'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 1376651 Extra: Using where 1 row in set (0.00 sec) Assuming there were no previously created indexes, which would the optimal one be? Assuming there were no previously created indexes, which would the optimal one be?
  • 78. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 78 Options for indexes ● 1 index on column (rev_page) ● 1 index on column (rev_timestamp) ● 2 indexes, 1 on (rev_page) and another on (rev_timestamp) ● 1 multi-column index on (rev_page, rev_timestamp) ● 1 multi-column index on (rev_timestamp,rev_page) Are these last 2 different from each other? Would it depend on the query order? Are these last 2 different from each other? Would it depend on the query order?
  • 79. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 79 A brief reminder about query parsing SELECT * FROM revision WHERE rev_page = 31579 and rev_timestamp < '2008' SELECTSELECT FieldsFields FROMFROM revision. rev_page revision. rev_page == (int constant) 31579 (int constant) 31579 revision. rev_timest amp revision. rev_timest amp << (string constant) '2008' (string constant) '2008' WHEREWHERE revision. rev_id revision. rev_id ... revisionrevision Order in the query is not important Order in the query is not important
  • 80. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 80 Index on (rev_page) MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX rev_page (rev_page); Query OK, 0 rows affected (11.87 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page = 31579 and rev_timestamp < '2008'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ref possible_keys: rev_page key: rev_page key_len: 4 ref: const rows: 4038 Extra: Using where 1 row in set (0.00 sec) Query time improves significantly with this index Query time improves significantly with this index Fewer rows are scanned Fewer rows are scanned
  • 81. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 81 Adding (rev_timestamp) MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX rev_timestamp (rev_timestamp); Query OK, 0 rows affected (17.01 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page = 31579 and rev_timestamp < '2008'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ref possible_keys: rev_page,rev_timestamp key: rev_page key_len: 4 ref: const rows: 4038 Extra: Using where 1 row in set (0.01 sec) rev_page is preferred over rev_timestamp rev_page is preferred over rev_timestamp In general, only one index can be used per table access In general, only one index can be used per table access
  • 82. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 82 Forcing the use of (rev_timestamp) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision FORCE INDEX(rev_timestamp) WHERE rev_page = 31579 and rev_timestamp < '2008'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: rev_timestamp key: rev_timestamp key_len: 14 ref: NULL rows: 688325 Extra: Using index condition; Using where 1 row in set (0.00 sec) It is a range access It is a range access A lot more accessed rows A lot more accessed rows Forcing the index is worse than type:ALL!Forcing the index is worse than type:ALL!
  • 83. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 83 Adding (rev_page, rev_timestamp)MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX rev_page_rev_timestamp(rev_page, rev_timestamp); Query OK, 0 rows affected (14.02 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page = 31579 and rev_timestamp < '2008'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: rev_page,rev_timestamp,rev_page_rev_timestamp key: rev_page_rev_timestamp key_len: 18 ref: NULL rows: 530 Extra: Using index condition 1 row in set (0.00 sec) Reduced number of rows scanned Reduced number of rows scanned
  • 84. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 84 Is (rev_timestamp, rev_page) a better option?MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX rev_timestamp_rev_page (rev_timestamp, rev_page); Query OK, 0 rows affected (16.80 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page = 31579 and rev_timestamp < '2008'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: rev_page,rev_timestamp,rev_page_rev_timestamp,rev_timestamp_rev_page key: rev_page_rev_timestamp key_len: 18 ref: NULL rows: 530 Extra: Using index condition 1 row in set (0.00 sec) Previous index is still preferred, why?
  • 85. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 85 Forcing (rev_timestamp, rev_page) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision FORCE INDEX(rev_timestamp_rev_page) WHERE rev_page = 31579 and rev_timestamp < '2008'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: rev_timestamp_rev_page key: rev_timestamp_rev_page key_len: 18 ref: NULL rows: 688325 Extra: Using index condition 1 row in set (0.00 sec) Only the 1st column* is being used effectively for filtering
  • 86. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 86 A compound index produces a single tree ordered by the 2 values (12301, '2018') (12301, '2018') (31579, '2005') (31579, '2005') (8005, '2016') (8005, '2016') (15444, '2011') (15444, '2011') (31579, '2003') (31579, '2003') (31579, '2017') (31579, '2017') (4611, '2015')(4611, '2015') (12702, '2014')(12702, '2014') (30024, '2003')(30024, '2003') (31579, '2008')(31579, '2008') (11771, '2013')(11771, '2013') (23305, '2006')(23305, '2006') (31579, '2004')(31579, '2004') (428105, '2015')(428105, '2015') TABLETABLE (23443, '2016') (23443, '2016') rev_page = 31579 and rev_timestamp > '2008' (rev_page, rev_timestamp) (rev_page, rev_timestamp)
  • 87. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 87 The alternative index cannot be used in such an effective way ('2008', 800)('2008', 800) ('2016', 24)('2016', 24) ('2004', 107)('2004', 107) ('2008', 55765) ('2008', 55765) ('2014', 999)('2014', 999) ('2018', 31579) ('2018', 31579) ('2001', 355)('2001', 355) ('2008', 31579)('2008', 31579) ('2012', 791)('2012', 791) ('2018', 1784)('2018', 1784) ('2008', 206)('2008', 206) ('2009', 201)('2009', 201) ('2015', 31579)('2015', 31579) ('2018', 79405)('2018', 79405) TABLETABLE ('2011', 13605) ('2011', 13605) (rev_timestamp, rev_page) (rev_timestamp, rev_page) rev_page = 31579 and rev_timestamp > '2008'
  • 88. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 88 Order and column selection • Range access using >, <, >=, <=, BETWEEN can only be filtered once effectively, at the end of an index • When selecting indexes, prefer columns with high cardinality (very selective) – The optimal index can depend on the constants used
  • 89. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 89 Can we filter and sort at the same time using indexes? MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_comment='' ORDER BY rev_timestamp ASCG *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 817636 Extra: Using where; Using filesort 1 row in set (0.00 sec) This query is slow because a) the full table scan This query is slow because a) the full table scan b) Required sort after filtering b) Required sort after filtering
  • 90. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 90 Adding an index on (rev_comment, rev_timestamp)MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX rev_comment_rev_timestamp (rev_comment, rev_timestamp); Query OK, 0 rows affected (16.19 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_comment='' ORDER BY rev_timestamp ASCG *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ref possible_keys: rev_comment_rev_timestamp key: rev_comment_rev_timestamp key_len: 769 ref: const rows: 266462 Extra: Using index condition; Using where 1 row in set (0.00 sec) Both ‘type: ALL’ and ‘filesort’ have disappeared Both ‘type: ALL’ and ‘filesort’ have disappeared
  • 91. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 91 This is not always possible MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_len > 5500 ORDER BY rev_timestamp ASCG *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: rev_len_rev_timestamp key: rev_len_rev_timestamp key_len: 5 ref: NULL rows: 38744 Extra: Using index condition; Using filesort 1 row in set (0.00 sec) The range makes impossible to use the index optimally for the ORDER BY: either we filter (rev_len) or sort (rev_timestamp) The range makes impossible to use the index optimally for the ORDER BY: either we filter (rev_len) or sort (rev_timestamp)
  • 92. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 92 A Strange Game. The Only Winning Move is Not to Play mysql (osm) > SELECT * FROM nodes FORCE INDEX(version_idx) WHERE version < 15 ORDER BY changeset_id; /* type: range, Using filesort */ 2859673 rows in set (30.58 sec) mysql (osm) > SELECT * FROM nodes FORCE INDEX(changeset_id_idx) WHERE version < 15 ORDER BY changeset_id; /* type: index */ 2859673 rows in set (30.92 sec) mysql (osm) > SELECT * FROM nodes WHERE version < 15 ORDER BY changeset_id; /* type: ALL, Using filesort */ 2859673 rows in set (16.54 sec)
  • 93. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 95 DESC Indexes (new in 8.0) ● Regular indexes could also be used to scan rows in inverse order – It had a penalty – It improves certain queries combining DESC and ASC order mysql-8.0 [dewiktionary]> ALTER TABLE page ADD INDEX `page_redirect_namespace_len_DESC` (`page_is_redirect`,`page_namespace`,`page_len` DESC); Query OK, 0 rows affected, 1 warning (3.96 sec) Records: 0 Duplicates: 0 Warnings: 1
  • 94. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 96 Descending Indexes in Action mysql-8.0 [dewiktionary]> EXPLAIN SELECT * FROM page WHERE page_is_redirect = 1 ORDER BY page_namespace, page_len DESCG *************************** 1. row *************************** id: 1 select_type: SIMPLE table: page type: ref possible_keys: page_redirect_namespace_len,page_redirect_namespace_len_DESC key: page_redirect_namespace_len key_len: 1 ref: const rows: 2404 Extra: Using where 1 row in set (0.00 sec) Filesort avoided Filesort avoided
  • 95. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 97 Index Condition Pushdown MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX rev_len_rev_timestamp(rev_len, rev_timestamp); Query OK, 0 rows affected (18.11 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> SET optimizer_switch='index_condition_pushdown=on'; EXPLAIN SELECT * FROM revision WHERE rev_timestamp < '2008' AND rev_len > 10000G Query OK, 0 rows affected (0.00 sec) *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: rev_timestamp,rev_timestamp_rev_page,rev_len_rev_timestamp key: rev_len_rev_timestamp key_len: 5 ref: NULL rows: 180270 Extra: Using index condition 1 row in set (0.00 sec) Index condition pushdown (ICP) enables the engines to use extra parts of the index while avoiding costly row movements to and from the SQL layer Index condition pushdown (ICP) enables the engines to use extra parts of the index while avoiding costly row movements to and from the SQL layer
  • 96. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 98 ICP optimizations ● Differences in execution time are more significant when the extra column condition is very selective (getting 5x the original performance) ● ICP is ignored when using covering Index, potentially making the performance worse
  • 97. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 99 Use of "Handler_*" statistics • They are post-execution statistics at row level – Unlike EXPLAIN's "rows" column, they are exact, not a guess – They allow comparison of query execution performance in a deterministic way, independently of the execution time
  • 98. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 100 "Handler" Stats (indexed) mysql> SHOW SESSION STATUS like 'Hand%'; +----------------------------+----------+ | Variable_name | Value | +----------------------------+----------+ | Handler_commit | 80 | | Handler_delete | 0 | | Handler_discover | 0 | | Handler_external_lock | 166 | | Handler_mrr_init | 0 | | Handler_prepare | 0 | | Handler_read_first | 23 | | Handler_read_key | 736212 | | Handler_read_last | 0 | | Handler_read_next | 22208001 | | Handler_read_prev | 0 | | Handler_read_rnd | 665215 | | Handler_read_rnd_next | 14223297 | | Handler_rollback | 0 | | Handler_savepoint | 0 | | Handler_savepoint_rollback | 0 | | Handler_update | 66970 | | Handler_write | 2869409 | +----------------------------+----------+ 18 rows in set (0.00 sec) Number of times that the first entry of an index was read. It may indicate the number of full index scans Number of times that the first entry of an index was read. It may indicate the number of full index scans Number of times a row has been retrieved using an index Number of times a row has been retrieved using an index Next row has been requested in index order (typical for index scans or ranges) Next row has been requested in index order (typical for index scans or ranges)
  • 99. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 101 "Handler" Stats (unindexed) mysql> SHOW SESSION STATUS like 'Hand%'; +----------------------------+----------+ | Variable_name | Value | +----------------------------+----------+ | Handler_commit | 80 | | Handler_delete | 0 | | Handler_discover | 0 | | Handler_external_lock | 166 | | Handler_mrr_init | 0 | | Handler_prepare | 0 | | Handler_read_first | 23 | | Handler_read_key | 736212 | | Handler_read_last | 0 | | Handler_read_next | 22208001 | | Handler_read_prev | 0 | | Handler_read_rnd | 665215 | | Handler_read_rnd_next | 14223297 | | Handler_rollback | 0 | | Handler_savepoint | 0 | | Handler_savepoint_rollback | 0 | | Handler_update | 66970 | | Handler_write | 2869409 | +----------------------------+----------+ 18 rows in set (0.00 sec) A row has been requested in a specific position (typical for joins or order by without indexes) A row has been requested in a specific position (typical for joins or order by without indexes) Request to read the next row in “table order” (typical for full table scans) Request to read the next row in “table order” (typical for full table scans) Insertions in SELECTS may indicate temporary tables Insertions in SELECTS may indicate temporary tables
  • 100. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 102 Comparing statistics of the previous indexes (no indexes)MariaDB [dewiktionary]> FLUSH STATUS; Query OK, 0 rows affected (0.00 sec) MariaDB [dewiktionary]> SELECT * FROM revision IGNORE INDEX(rev_page, rev_timestamp, rev_page_rev_timestamp, rev_timestamp_rev_page) WHERE rev_page = 31579 and rev_timestamp < '2008'; 530 rows in set (0.71 sec) MariaDB [dewiktionary]> SHOW STATUS like 'Hand%'; +----------------------------+---------+ | Variable_name | Value | +----------------------------+---------+ | Handler_commit | 1 | | Handler_delete | 0 | … | Handler_read_first | 0 | | Handler_read_key | 0 | | Handler_read_last | 0 | | Handler_read_next | 0 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 1430043 | | Handler_rollback | 0 | … | Handler_update | 0 | | Handler_write | 0 | +----------------------------+---------+ 27 rows in set (0.00 sec) Typical result for a full table scan Typical result for a full table scan
  • 101. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 103 Index on (rev_page) MariaDB [dewiktionary]> SHOW STATUS like 'Hand%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_commit | 1 | | Handler_delete | 0 | … | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_last | 0 | | Handler_read_next | 4038 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | … | Handler_update | 0 | | Handler_write | 0 | +----------------------------+-------+ 27 rows in set (0.01 sec) Then, scan them one by one in index order Then, scan them one by one in index order Using the index, request the first row with rev_page=31579 Using the index, request the first row with rev_page=31579
  • 102. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 104 Index on (rev_timestamp) MariaDB [dewiktionary]> SHOW STATUS like 'Hand%'; +----------------------------+---------+ | Variable_name | Value | +----------------------------+---------+ | Handler_commit | 1 | | Handler_delete | 0 | … | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_last | 0 | | Handler_read_next | 199155 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | … +----------------------------+---------+ 27 rows in set (0.00 sec) Then, scan them one by one in index order (more are matched) Then, scan them one by one in index order (more are matched) Using the index, request the first row where rev_timestamp<2008 Using the index, request the first row where rev_timestamp<2008 ICP will be explained later, let's ignore it for now ICP will be explained later, let's ignore it for now
  • 103. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 105 Index on (rev_page, rev_timestamp)MariaDB [dewiktionary]> SHOW STATUS like 'Hand%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_commit | 1 | | Handler_delete | 0 | … | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_last | 0 | | Handler_read_next | 530 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | … | Handler_update | 0 | | Handler_write | 0 | +----------------------------+-------+ 25 rows in set (0.00 sec) Rows scanned == Rows returnedRows scanned == Rows returned With both conditions covered, we can find the actual first row that matches the condition using the index With both conditions covered, we can find the actual first row that matches the condition using the index
  • 104. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 106 Index on (rev_timestamp, rev_page), no ICPMariaDB [dewiktionary]> SHOW STATUS like 'Hand%'; +----------------------------+---------+ | Variable_name | Value | +----------------------------+---------+ | Handler_commit | 1 | | Handler_delete | 0 | … | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_last | 0 | | Handler_read_next | 452539 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | … | Handler_update | 0 | | Handler_write | 0 | +----------------------------+---------+ 27 rows in set (0.00 sec) Assuming no ICP, exact same results as with (rev_timestamp). The extra column does not help. Also, EXPLAIN's row count was very off. Assuming no ICP, exact same results as with (rev_timestamp). The extra column does not help. Also, EXPLAIN's row count was very off.
  • 105. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 107 Index on (rev_timestamp, rev_page), with ICPMariaDB [dewiktionary]> SHOW STATUS like 'Hand%'; +----------------------------+--------+ | Variable_name | Value | +----------------------------+--------+ | Handler_commit | 1 | | Handler_delete | 0 | … | Handler_icp_attempts | 452539 | | Handler_icp_match | 530 | … | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_last | 0 | | Handler_read_next | 530 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | … | Handler_update | 0 | | Handler_write | 0 | +----------------------------+--------+ 27 rows in set (0.00 sec) ICP reduces the number of ‘ENGINE API calls’ significantly, although making it work more internally ICP reduces the number of ‘ENGINE API calls’ significantly, although making it work more internally
  • 106. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 108 Redundant Indexes ● Creating all 4 previous indexes in production is not a great idea – "Left-most index prefix" allows, for example (rev_page, rev_timestamp) doing everything you can do with (rev_page) – If two indexes have equal selectivity, MySQL chooses the shortest one
  • 107. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 109 "Left-most index" Example MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision FORCE INDEX(rev_page_rev_timestamp) WHERE rev_page = 31579G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ref possible_keys: rev_page_rev_timestamp key: rev_page_rev_timestamp key_len: 4 ref: const rows: 4038 Extra: 1 row in set (0.00 sec) Only the first column is usedOnly the first column is used
  • 108. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 110 Duplicate Indexes ● It is very easy to create indexes with the same exact definition (same columns and ordering) – Set a convention for index naming (e.g tablename_column1_column2_idx) – MySQL does not allow 2 indexes with the same identifier – Since MySQL 5.6, an warning is thrown if a duplicate index is created: Duplicate index 'page_random2' defined on the table 'dewiktionary.page'. This is deprecated and will be disallowed in a future release. Still a warning on 8.0Still a warning on 8.0
  • 109. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 111 pt-duplicate-index-checker $ pt-duplicate-key-checker h=localhost,u=root,D=dewiktionary […] # rev_timestamp is a left-prefix of rev_timestamp_rev_page # Key definitions: # KEY `rev_timestamp` (`rev_timestamp`), # KEY `rev_timestamp_rev_page` (`rev_timestamp`,`rev_page`) # Column types: # `rev_timestamp` binary(14) not null default '00000000000000' # `rev_page` int(10) unsigned not null # To remove this duplicate index, execute: ALTER TABLE `dewiktionary`.`revision` DROP INDEX `rev_timestamp`; # rev_page is a left-prefix of rev_page_rev_timestamp # Key definitions: # KEY `rev_page` (`rev_page`), # KEY `rev_page_rev_timestamp` (`rev_page`,`rev_timestamp`), # Column types: # `rev_page` int(10) unsigned not null # `rev_timestamp` binary(14) not null default '00000000000000' # To remove this duplicate index, execute: ALTER TABLE `dewiktionary`.`revision` DROP INDEX `rev_page`; # ############################################# Summary of indexes ######################################## # Size Duplicate Indexes 47836942 # Total Duplicate Indexes 4 # Total Indexes 285 Simple tool to check redundant and duplicate indexes Simple tool to check redundant and duplicate indexes
  • 110. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 112 Invisible Indexes (new in 8.0) ● Before removing a (believed) redundant or duplicate index, make it “invisible” to prevent long-running outages: mysql-8.0 [dewiktionary]> ALTER TABLE page ALTER INDEX page_len INVISIBLE; Query OK, 0 rows affected (0.04 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql-8.0 [dewiktionary]> SELECT * FROM page FORCE INDEX(page_len); ERROR 1176 (42000): Key 'page_len' doesn't exist in table 'page'
  • 111. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 113 "OR"-style conditions over the same column MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page = 790 OR rev_page = 795 OR rev_page = 1024G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: rev_page,rev_page_rev_timestamp key: rev_page key_len: 4 ref: NULL rows: 201 Extra: Using index condition; Using where 1 row in set (0.01 sec) Equivalent to: SELECT * FROM revision WHERE rev_page IN (790, 795, 1024) Equivalent to: SELECT * FROM revision WHERE rev_page IN (790, 795, 1024)
  • 112. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 114 Handlers on "IN" / "OR" conditions over the same column+----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_commit | 1 | | Handler_delete | 0 | … | Handler_prepare | 0 | | Handler_read_first | 0 | | Handler_read_key | 3 | | Handler_read_last | 0 | | Handler_read_next | 201 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | … | Handler_update | 0 | | Handler_write | 0 | +----------------------------+-------+ 25 rows in set (0.00 sec) Despite identifying themselves as “range”s, the execution is slightly different, one index dive (similar to a ref) is done per value. This can be an issue in conditions with thousands of items. Despite identifying themselves as “range”s, the execution is slightly different, one index dive (similar to a ref) is done per value. This can be an issue in conditions with thousands of items.
  • 113. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 115 "OR"-style conditions over different columns ● We cannot use a single index efficiently for both conditions – We can scan both conditions separately and mix the results, discarding duplicates – Or use an index for one condition and not for the other – Index merge allows the use of two indexes for a single table access simultaneously
  • 114. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 116 Index Merge Example MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page = 790 or rev_timestamp < '2004'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: index_merge possible_keys: rev_page,rev_timestamp,rev_page_rev_timestamp, rev_timestamp_rev_page key: rev_page,rev_timestamp key_len: 4,14 ref: NULL rows: 190 Extra: Using sort_union(rev_page,rev_timestamp); Using where 1 row in set (0.00 sec) Both indexes are used, then combined using the "union" operation Both indexes are used, then combined using the "union" operation
  • 115. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 117 INDEX_MERGE Issues ● Sometimes it is faster to to execute the sentence using UNION: – This is especially true with (UNION ALL) since MySQL 5.7, if you do not care or expect duplicates ● There are also intersection merges, but multi-column indexes are preferred
  • 116. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 118 Disabling optimizer features (I) ● The optimizer_switch variable allows enabling and disabling globally or per session many query optimizer features: MariaDB [nlwiktionary]> SHOW VARIABLES like 'optimizer_switch'G *************************** 1. row *************************** Variable_name: optimizer_switch Value: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection= on,index_merge_sort_intersection=off,engine_condition_pushdown=off,index_condition_push down=on,derived_merge=on,derived_with_keys=on,firstmatch=on,loosescan=on,materializatio n=on,in_to_exists=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan= on,subquery_cache=on,mrr=off,mrr_cost_based=off,mrr_sort_keys=off,outer_join_with_cache =on,semijoin_with_cache=on,join_cache_incremental=on,join_cache_hashed=on,join_cache_bk a=on,optimize_join_buffer_size=off,table_elimination=on,extended_keys=on,exists_to_in=o n,orderby_uses_equalities=on,condition_pushdown_for_derived=on,split_materialized=on 1 row in set (0.00 sec)
  • 117. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 119 Disabling optimizer features (II) MariaDB [dewiktionary]> SET optimizer_switch='index_merge_sort_union=off'; Query OK, 0 rows affected (0.00 sec) MariaDB [dewiktionary]> EXPLAIN SELECT * FROM revision WHERE rev_page = 790 or rev_timestamp < '2004'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ALL possible_keys: rev_page,rev_timestamp,rev_page_rev_timestamp,rev_timestamp_rev_page key: NULL key_len: NULL ref: NULL rows: 1376651 Extra: Using where 1 row in set (0.00 sec) This will only have effect for the current session. This will only have effect for the current session.
  • 118. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 120 What happens if we have two ranges? ● As seen previously, we cannot use efficiently two range types on the same table access. Alternatives: – Use only one index for the most selective column – Use index condition pushdown to get an advantage – Change one of the two ranges into a discrete "IN" comparison/bucketize with a new column – Use quadtrees or R-TREEs (spatial indexing)
  • 119. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 121 Example of Bucketizing (I) MariaDB [dewiktionary]> ALTER TABLE revision DROP INDEX rev_len_rev_timestamp; MariaDB [dewiktionary]> EXPLAIN SELECT count(*) FROM revision WHERE rev_timestamp < '2008' AND rev_len > 5500G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: ALL possible_keys: rev_timestamp,rev_timestamp_rev_page key: NULL key_len: NULL ref: NULL rows: 1376651 Extra: Using where 1 row in set (0.00 sec) Looks like only an index on (rev_timestamp) or (rev_len) would be useful as we have 2 ranges. Looks like only an index on (rev_timestamp) or (rev_len) would be useful as we have 2 ranges.
  • 120. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 122 Example of Bucketizing (II) MariaDB [dewiktionary]> ALTER TABLE revision ADD COLUMN rev_len_cat int; Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [dewiktionary]> UPDATE revision set rev_len_cat = IF(rev_len < 10000, rev_len div 1000, 10); Query OK, 1430042 rows affected (21.18 sec) Rows matched: 1430042 Changed: 1430042 Warnings: 0 MariaDB [dewiktionary]> ALTER TABLE revision ADD INDEX rev_len_cat_rev_timestamp (rev_len_cat, rev_timestamp); Query OK, 0 rows affected (7.77 sec) Records: 0 Duplicates: 0 Warnings: 0 8.0/10.3 instant alter-nice! 8.0/10.3 instant alter-nice!
  • 121. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 123 Example of Bucketizing (III) MariaDB [dewiktionary]> EXPLAIN SELECT count(*) FROM revision WHERE rev_timestamp < '2008' AND rev_len > 5500 AND rev_len_cat IN (5, 6, 7, 8, 9, 10)G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: rev_timestamp,rev_timestamp_rev_page,rev_len_cat_rev_timestamp key: rev_len_cat_rev_timestamp key_len: 19 ref: NULL rows: 77601 Extra: Using where 1 row in set (0.00 sec) We did some transformations to both the structure and the query. We did some transformations to both the structure and the query.
  • 122. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 124 Even better: Functional Indexes (I) MariaDB [dewiktionary]> ALTER TABLE revision DROP INDEX rev_len_cat_rev_timestamp, DROP COLUMN rev_len_cat, ADD COLUMN rev_len_cat_virtual int as (IF(rev_len < 10000, rev_len div 1000, 10)) VIRTUAL, ADD INDEX rev_len_cat_virtual_rev_timestamp (rev_len_cat_virtual, rev_timestamp); Query OK, 1430042 rows affected (1 min 42.35 sec) Records: 1430042 Duplicates: 0 Warnings: 0 Storing the extra content may be a waste of resources, lets make it a virtual column Storing the extra content may be a waste of resources, lets make it a virtual column Indexing virtual columns, new in 10.3 Indexing virtual columns, new in 10.3
  • 123. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 125 Even better: Functional Indexes (II) MariaDB [dewiktionary]> EXPLAIN SELECT count(*) FROM revision WHERE rev_timestamp < '2008' AND rev_len > 5500 AND rev_len_cat_virtual IN (5, 6, 7, 8, 9, 10)G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: revision type: range possible_keys: rev_timestamp,rev_timestamp_rev_page,rev_len_cat_virtual_rev_timestamp key: rev_len_cat_virtual_rev_timestamp key_len: 19 ref: NULL rows: 78649 Extra: Using where 1 row in set (0.01 sec) We did some transformations to both the structure and the query. We did some transformations to both the structure and the query.
  • 124. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 126 MyISAM Internals node_id versio k v 234234344545 1 leaves deciduous (empty row) 234234344548 5 gps:latitude 490018321N 234234344549 6 access uncontrolled (empty row) 234234344551 9 name Big Ben 234234344552 1 source survey 234234344557 1 name London Plane 234234344552 2 source survey 234234344557 2 name London Plane CarlsbergCarlsberg recyclingrecycling Accurate as of 2010 Accurate as of 2010 Gwynne Place Gwynne Place Manor PlaceManor Place treetree 490018321N490018321N deciduousdeciduous London PlaneLondon Plane surveysurvey Big BenBig Ben JOSMJOSM Northolt RoadNortholt Road uncontrolleduncontrolled London Borough of Southwark London Borough of Southwark Data (revision.MYD)Index (part of revision.MYI)
  • 125. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 127 InnoDB Internals (PRIMARY) Data clustered always using the primary key Data clustered always using the primary key 244 208 105 476 356 702 605 811 771 702 476 244 version k v 1 leaves deciduous 3 name Northolt Road 5 gps:latitu 490018321N 6 access uncontrolled 1 editor JOSM 9 name Big Ben 2 source survey 1 name London Plane 7 amenity pub
  • 126. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 128 InnoDB Internals (Secondary) 6 3 1 1 1 1 3 2 6 5 9 7 PK 356 105 702 811 476 208 Secondary indexes contain the primary key value Secondary indexes contain the primary key value PK / DataIndex (version) 244 208 105 476 356 702 605 811 771 702 476 244 version k v 1 leaves deciduous 3 name Northolt Road 5 gps:latitu 490018321N 6 access uncontrolled 1 editor JOSM 9 name Big Ben 2 source survey 1 name London Plane 7 amenity pub 605 244 771
  • 127. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 129 Consequences of using InnoDB (I) ● Every table should have a primary key – If one is not defined, MySQL will choose an available NOT NULL unique key – If that is not possible, an internal 6-byte row identifier will be generated (not user-accesible)
  • 128. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 130 Consequences of using InnoDB (II) ● Inserting in primary key order is much faster – Less fragmentation/page-split – Usage of "batch" mode, improving insert speed ● Using auto-increment keys as primary keys can be a good idea for InnoDB
  • 129. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 131 Consequences of using InnoDB (III) • A very long primary key may increment substantially the size of secondary keys – Int or bigint types are recommended instead of UUIDs or other long strings
  • 130. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 132 Differences in size mysql (osm) > CREATE TABLE pk_int (id int PRIMARY KEY auto_increment, a int, b int, c int, d int); Query OK, 0 rows affected (0.16 sec) mysql (osm) > CREATE TABLE pk_uuid (id char(36) PRIMARY KEY, a int, b int, c int, d int); Query OK, 0 rows affected (0.04 sec) pk_int pk_uuid 0 8500000 17000000 25500000 34000000 42500000 12,582,912 20,971,520 13,631,488 32,505,856 .ibd size (no secondary indexes) .ibd size (with secondary indexes) bytes
  • 131. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 133 Extended primary key optimization ● As the primary key is part of all secondary keys, this can be used “for free”: – For row filtering (since MySQL 5.6) – To return results in primary key order – To avoid reading data from the table (covering index)
  • 132. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 134 Extended Primary Key Example mysql (osm) > EXPLAIN SELECT node_id FROM nodes WHERE changeset_id = 24284 and node_id <> 146472942G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: nodes type: range possible_keys: PRIMARY,changeset_id_idx key: changeset_id_idx key_len: 16 ref: NULL rows: 50 Extra: Using where; Using index 1 row in set (0.07 sec)
  • 133. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 135 JOINS Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
  • 134. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 136 (Block) Nested Loop Join ● Until MySQL 5.5 there was only one algorithm to execute a JOIN: node_id version lat lon 1 1 52 0.5 1 2 52 0.5 2 1 51 1 3 1 53 1.5 node_id version k v 1 1 name Big Benn 1 1 tourism attraction 1 2 name Big Ben 1 2 tourism attraction 3 1 name London Eye
  • 135. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 137 Extra Access type: eq_ref mysql (osm) > EXPLAIN SELECT * FROM nodes JOIN node_tags USING(node_id, version) WHERE node_tags.v= 'Big Ben'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: node_tags type: ref possible_keys: PRIMARY,v_idx key: v_idx key_len: 767 ref: const rows: 1 Extra: Using where; Using index *************************** 2. row *************************** id: 1 select_type: SIMPLE table: nodes type: eq_ref possible_keys: PRIMARY,version_idx key: PRIMARY key_len: 16 ref: osm.node_tags.node_id,osm.node_tags.version rows: 1 Extra: NULL 2 rows in set (0.00 sec) eq_ref is similar to ref, but allows faster JOINS because, by using a unique key, it only has to search one row for each previous result eq_ref is similar to ref, but allows faster JOINS because, by using a unique key, it only has to search one row for each previous result
  • 136. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 138 JOIN Optimization ● Two main goals: – Perform an effective filtering on each table access, if possible using indexes – Perform the access in the most efficient table order ● When joining 3 or more tables in a star schema, the "covering index" strategy can have a huge impact
  • 137. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 139 Example: optimize this JOIN (I) SELECT n.node_id, n.latitude, n.longitude FROM way_nodes w_n JOIN way_tags w_t ON w_n.way_id = w_t.way_id and w_n.version = w_t.version JOIN nodes n ON w_n.node_id = n.node_id JOIN node_tags n_t ON n.node_id = n_t.node_id and n.version = n_t.version WHERE w_t.k = 'building' and n_t.k = 'entrance' and n_t.v = 'main'; We start without secondary indexes We start without secondary indexes
  • 138. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 140 Example: optimize this JOIN (II) ************************ 1. row ************************ id: 1 select_type: SIMPLE table: w_t type: index possible_keys: PRIMARY key: PRIMARY key_len: 783 ref: NULL rows: 1335702 Extra: Using where; Using index ************************ 2. row ************************ id: 1 select_type: SIMPLE table: w_n type: ref possible_keys: PRIMARY key: PRIMARY key_len: 16 ref: osm.w_t.way_id,osm.w_t.version rows: 3 Extra: NULL mysql (osm) > SELECT … 858 rows in set (9.00 sec) ************************ 3. row ************************ id: 1 select_type: SIMPLE table: n_t type: ref possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: osm.w_n.node_id rows: 1 Extra: Using where ************************ 4. row ************************ id: 1 select_type: SIMPLE table: n type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 16 ref: osm.w_n.node_id,osm.n_t.version rows: 1 Extra: Using index 4 rows in set (0.01 sec)
  • 139. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 141 Example: optimize this JOIN (III) mysql (osm) > ALTER TABLE way_tags ADD INDEX k_idx(k); Query OK, 0 rows affected (4.80 sec) Records: 0 Duplicates: 0 Warnings: 0 Creating an index on way_tags.k Creating an index on way_tags.k
  • 140. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 142 Example: optimize this JOIN (IV) ********************** 1. row ********************** id: 1 select_type: SIMPLE table: w_t type: ref possible_keys: PRIMARY,k_idx key: k_idx key_len: 767 ref: const rows: 452274 Extra: Using where; Using index ********************** 2. row ********************** id: 1 select_type: SIMPLE table: w_n type: ref possible_keys: PRIMARY key: PRIMARY key_len: 16 ref: osm.w_t.way_id,osm.w_t.version rows: 3 Extra: NULL mysql (osm) > SELECT … 858 rows in set (8.58 sec) It seems like the index is not very useful It seems like the index is not very useful ********************** 3. row ********************** id: 1 select_type: SIMPLE table: n_t type: ref possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: osm.w_n.node_id rows: 1 Extra: Using where ********************** 4. row ********************** id: 1 select_type: SIMPLE table: n type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 16 ref: osm.w_n.node_id,osm.n_t.version rows: 1 Extra: NULL 4 rows in set (0.00 sec)
  • 141. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 143 Example: optimize this JOIN (V) mysql (osm) > ALTER TABLE node_tags ADD INDEX k_idx(k); Query OK, 0 rows affected (2.82 sec) Records: 0 Duplicates: 0 Warnings: 0 The order does not seem to be adequate, let's try adding an index to start by accessing node_tags The order does not seem to be adequate, let's try adding an index to start by accessing node_tags
  • 142. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 144 Example: optimize this JOIN (VI) ********************** 1. row ********************** id: 1 select_type: SIMPLE table: w_t type: ref possible_keys: PRIMARY,k_idx key: k_idx key_len: 767 ref: const rows: 452274 Extra: Using where; Using index ********************** 2. row ********************** id: 1 select_type: SIMPLE table: w_n type: ref possible_keys: PRIMARY key: PRIMARY key_len: 16 ref: osm.w_t.way_id,osm.w_t.version rows: 3 Extra: NULL mysql (osm) > SELECT … 858 rows in set (7.33 sec) ********************** 3. row ********************** id: 1 select_type: SIMPLE table: n_t type: ref possible_keys: PRIMARY,k_idx key: PRIMARY key_len: 8 ref: osm.w_n.node_id rows: 1 Extra: Using where ********************** 4. row ********************** id: 1 select_type: SIMPLE table: n type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 16 ref: osm.w_n.node_id,osm.n_t.version rows: 1 Extra: NULL 4 rows in set (0.00 sec) It keeps using the wrong order, even if we delete the w_t.k_idx index It keeps using the wrong order, even if we delete the w_t.k_idx index
  • 143. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 145 Example: optimize this JOIN (VII) SELECT STRAIGHT_JOIN n.node_id, n.latitude, n.longitude FROM node_tags n_t JOIN nodes n ON n.node_id = n_t.node_id and n.version = n_t.version JOIN way_nodes w_n ON w_n.node_id = n.node_id JOIN way_tags w_t ON w_n.way_id = w_t.way_id and w_n.version = w_t.version WHERE w_t.k = 'building' and n_t.k = 'entrance' and n_t.v = 'main'; Let's see why rewriting it into this query Let's see why rewriting it into this query
  • 144. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 146 Example: optimize this JOIN (VIII) ********************** 1. row ********************** id: 1 select_type: SIMPLE table: n_t type: ref possible_keys: PRIMARY,k_idx key: k_idx key_len: 767 ref: const rows: 2390 Extra: Using index condition; Using where ********************** 2. row ********************** id: 1 select_type: SIMPLE table: n type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 16 ref: osm.n_t.node_id,osm.n_t.version rows: 1 Extra: NULL There is no index on w_n that would allow efficient access There is no index on w_n that would allow efficient access ********************** 3. row ********************** id: 1 select_type: SIMPLE table: w_n type: ALL possible_keys: PRIMARY key: NULL key_len: NULL ref: NULL rows: 3597858 Extra: Using where; Using join buffer (Block Nested Loop) ********************** 4. row ********************** id: 1 select_type: SIMPLE table: w_t type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 783 ref: osm.w_n.way_id,osm.w_n.version,const rows: 1 Extra: Using where; Using index 4 rows in set (0.00 sec)
  • 145. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 147 Example: optimize this JOIN (IX) mysql (osm) > ALTER TABLE way_nodes ADD INDEX node_id_idx(node_id); Query OK, 0 rows affected (17.77 sec) Records: 0 Duplicates: 0 Warnings: 0
  • 146. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 148 Example: optimize this JOIN (X) ********************** 1. row ********************** id: 1 select_type: SIMPLE table: n_t type: ref possible_keys: PRIMARY,k_idx key: k_idx key_len: 767 ref: const rows: 2390 Extra: Using index condition; Using where ********************** 2. row ********************** id: 1 select_type: SIMPLE table: n type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 16 ref: osm.n_t.node_id,osm.n_t.version rows: 1 Extra: NULL mysql (osm) > SELECT … 858 rows in set (0.73 sec) Now it starts with the right table (without STRAIGHT_JOIN) Now it starts with the right table (without STRAIGHT_JOIN) ********************** 3. row ********************** id: 1 select_type: SIMPLE table: w_n type: ref possible_keys: PRIMARY,node_id_idx key: node_id_idx key_len: 8 ref: osm.n_t.node_id rows: 1 Extra: Using index ********************** 4. row ********************** id: 1 select_type: SIMPLE table: w_t type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 783 ref: osm.w_n.way_id,osm.w_n.version,const rows: 1 Extra: Using where; Using index 4 rows in set (0.04 sec)
  • 147. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 149 Example: optimize this JOIN (XI) ************************* 1. row ************************* id: 1 select_type: SIMPLE table: n_t type: ref possible_keys: PRIMARY,k_idx,k_v_idx key: k_v_idx key_len: 1534 ref: const,const rows: 900 Extra: Using where; Using index ************************* 2. row ************************* id: 1 select_type: SIMPLE table: n type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 16 ref: osm.n_t.node_id,osm.n_t.version rows: 1 Extra: NULL mysql (osm) > SELECT ... 858 rows in set (0.02 sec) An index on (k,v) is even better An index on (k,v) is even better ************************ 3. row ************************ id: 1 select_type: SIMPLE table: w_n type: ref possible_keys: PRIMARY,node_id_idx key: node_id_idx key_len: 8 ref: osm.n_t.node_id rows: 1 Extra: Using index ************************ 4. row ************************ id: 1 select_type: SIMPLE table: w_t type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 783 ref: osm.w_n.way_id,osm.w_n.version,const rows: 1 Extra: Using where; Using index 4 rows in set (0.00 sec)
  • 148. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 150 “New” JOIN methods ● MySQL 5.6 added: – Batch Key Access ● MariaDB has since 5.3: – Batch Key Access – Hash Joins – Slightly modified versions of the above ones (with “incremental” buffers to join 3 or more tables)
  • 149. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 151 Multi-range read • This optimization orders results obtained from a secondary key in primary key/physical order before accessing the rows – It may help execution time of queries when disk-bound – It requires tuning of the read_rnd_buffer_size (size of the buffer used for ordering the results) • BKA JOINs are based on the mrr optimization
  • 150. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 152 MRR Example (I) mysql (osm) > EXPLAIN SELECT * FROM nodes WHERE timestamp >= '2013-07- 01 00:00:00' AND timestamp < '2014-01-01 00:00:00'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: nodes type: range possible_keys: nodes_timestamp_idx key: nodes_timestamp_idx key_len: 5 ref: NULL rows: 429684 Extra: Using index condition; Using MRR 1 row in set (0.02 sec)
  • 151. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 153 MRR example (II) [restart] mysql> SET optimizer_switch='mrr=off'; mysql> SELECT * FROM nodes WHERE timestamp >= '2013-07-01 00:00:00' AND timestamp < '2014-01-01 00:00:00'; 205617 rows in set (5.16 sec) mysql> SELECT * FROM nodes WHERE timestamp >= '2013-07-01 00:00:00' AND timestamp < '2014-01-01 00:00:00'; 205617 rows in set (0.60 sec) [restart] mysql> SET read_rnd_buffer_size=50 * 1024 * 1024; mysql> SELECT * FROM nodes WHERE timestamp >= '2013-07-01 00:00:00' AND timestamp < '2014-01-01 00:00:00'; 205617 rows in set (2.39 sec) mysql> SELECT * FROM nodes WHERE timestamp >= '2013-07-01 00:00:00' AND timestamp < '2014-01-01 00:00:00'; 205617 rows in set (0.73 sec) “Cold” results are significantly better with mrr (but it can impact negatively, too) “Cold” results are significantly better with mrr (but it can impact negatively, too)
  • 152. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 154 Batch Key Access node_id version lat lon 1 1 52 0.5 1 2 52 0.5 2 1 51 1 3 1 53 1.5 node_id version k v 1 1 name Big Benn 1 1 tourism attraction 1 2 name Big Ben 1 2 tourism attraction 3 1 amenity cafe Buffer sorted in physical/PK order
  • 153. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 155 Hash Joins • Only work for equi-joins node_id version lat lon 1 1 52 0.5 1 2 52 0.5 2 1 51 1 3 1 53 1.5 node_id version k v 1 1 name Big Benn 1 1 tourism attraction 1 2 name Big Ben 1 2 tourism attraction 3 1 amenity cafe Hash table for faster access to rows
  • 154. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 156 MySQL Configuration • BKA requires changes of default optimizer configuration: mysql-8.0 (osm) > SET optimizer_switch= 'mrr=on'; mysql-8.0 (osm) > SET optimizer_switch= 'mrr_cost_based=off'; mysql-8.0 (osm) > SET optimizer_switch= 'batch_key_access=on'; – Additionally, configuring the join_buffer_size adequately
  • 155. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 157 MariaDB configuration mariadb-10.3 (osm) > SET optimizer_switch = 'join_cache_incremental=on'; mariadb-10.3 (osm) > SET optimizer_switch = 'join_cache_hashed=on'; mariadb-10.3 (osm) > SET optimizer_switch = 'join_cache_bka=on'; - Enabled by default mariadb-10.3 (osm) > SET join_cache_level = 3 (for hash joins) mariadb-10.3 (osm) > SET join_cache_level = 5 (for BKA) - Also, configure join_buffer_size appropriately. - Hash joins, like BKA, are highly dependent on disk-bound DBs to be effective due to the extra overhead
  • 156. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 158 Nested Loop Join (cold buffers buffer_pool=100MB, join_buffer=4M) mariadb-10.3 (osm) > EXPLAIN SELECT changeset_id, count(*) FROM changesets JOIN nodes on changesets.id = nodes.changeset_id GROUP BY visibleG ******************** 1. row ******************** id: 1 select_type: SIMPLE table: changesets type: index possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: NULL rows: 69115 Extra: Using index; Using temporary; Using filesort ******************** 2. row ******************** id: 1 select_type: SIMPLE table: nodes type: ref possible_keys: changeset_id key: changeset_id key_len: 8 ref: osm.changesets.id rows: 19 Extra: 2 rows in set (0.00 sec) mariadb-10.3 (osm) > SELECT visible, count(*) FROM changesets JOIN nodes on changesets.id = nodes.changeset_id GROUP BY visible; +---------+----------+ | visible | count(*) | +---------+----------+ | 1 | 2865312 | +---------+----------+ 1 row in set (32.86 sec)
  • 157. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 159 Hash Join (cold buffers, buffer_pool=100M, join_buffer=4M) mariadb-10.3 (osm) > EXPLAIN SELECT changeset_id, count(*) FROM changesets JOIN nodes on changesets.id = nodes.changeset_id GROUP BY visibleG ******************* 1. row ******************* id: 1 select_type: SIMPLE table: changesets type: index possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: NULL rows: 69115 Extra: Using index; Using temporary; Using filesort ******************** 2. row ******************** id: 1 select_type: SIMPLE table: nodes type: hash_ALL possible_keys: changeset_id key: #hash#changeset_id key_len: 8 ref: osm.changesets.id rows: 2781732 Extra: Using join buffer (flat, BNLH join) 2 rows in set (0.00 sec) mariadb-10.3 (osm) > SELECT visible, count(*) FROM changesets JOIN nodes on changesets.id = nodes.changeset_id GROUP BY visible; +---------+----------+ | visible | count(*) | +---------+----------+ | 1 | 2865312 | +---------+----------+ 1 row in set (6.66 sec)
  • 158. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 160 SUBQUERIES Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
  • 159. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 161 Access types: unique_subquery/index_subquery mysql-8.0 (osm) > EXPLAIN SELECT * FROM node_tags WHERE v = 'Big Ben' and node_id NOT IN (SELECT node_id FROM nodes WHERE tile < 100000000)G ************** 1. row ************** id: 1 select_type: PRIMARY table: node_tags type: ref possible_keys: v_idx key: v_idx key_len: 767 ref: const rows: 1 Extra: Using where; Using index Unique subquery is similar, but using a unique or primary key Unique subquery is similar, but using a unique or primary key ************** 2. row ************** id: 2 select_type: DEPENDENT SUBQUERY table: nodes type: index_subquery possible_keys: PRIMARY,nodes_tile_idx key: PRIMARY key_len: 8 ref: func rows: 1 Extra: Using where 2 rows in set (0.00 sec)
  • 160. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 162 Subqueries in MySQL ● MySQL versions traditionally had very bad press regarding subquries – It was common to recommend rewriting them (when possible) into JOINS ● Since MySQL 5.6, its query execution plans have improved significantly
  • 161. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 163 Lazy Materialization of derived tables ● Option available since MySQL 5.6 – Improves the execution time of EXPLAIN (it no longer needs to execute subqueries) – Derived tables can be indexed automatically at execution time to improve its performance
  • 162. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 164 Derived Table Example mysql-5.5.40 (osm) > EXPLAIN SELECT count(*) FROM (SELECT * FROM nodes WHERE VISIBLE = 1) n JOIN changesets ON n.changeset_id = changesets.id; +----+-------------+------------+--------+---------------+---------+---------+----------------+---------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+--------+---------------+---------+---------+----------------+---------+-------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 2865312 | | | 1 | PRIMARY | changesets | eq_ref | PRIMARY | PRIMARY | 8 | n.changeset_id | 1 | Using index | | 2 | DERIVED | nodes | ALL | NULL | NULL | NULL | NULL | 2865521 | Using where | +----+-------------+------------+--------+---------------+---------+---------+----------------+---------+-------------+ 3 rows in set (1.42 sec) mysql-8.0 (osm) > EXPLAIN SELECT count(*) FROM (SELECT * FROM nodes WHERE VISIBLE = 1) n JOIN changesets ON n.changeset_id = changesets.id; +----+-------------+------------+-------+---------------+-------------+---------+-------------------+--------- +-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+---------------+-------------+---------+-------------------+--------- +-------------+ | 1 | PRIMARY | changesets | index | PRIMARY | PRIMARY | 8 | NULL | 70917 | Using index | | 1 | PRIMARY | <derived2> | ref | <auto_key0> | <auto_key0> | 8 | osm.changesets.id | 40 | NULL | | 2 | DERIVED | nodes | ALL | NULL | NULL | NULL | NULL | 2853846 | Using where | +----+-------------+------------+-------+---------------+-------------+---------+-------------------+--------- +-------------+ 3 rows in set (0.00 sec) Subquery is not executed Subquery is not executed Auto- generated index Auto- generated index
  • 163. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 165 A Common 5.5 Performance Problemmysql-5.5.40 (osm) > EXPLAIN SELECT * FROM nodes WHERE nodes.changeset_id IN ( SELECT changesets.id FROM changesets JOIN users ON changesets.user_id = users.id and users.display_name = 'Steve'); +----+--------------------+------------+--------+--------------------------------+------------------------+---------+-------+---------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+------------+--------+--------------------------------+------------------------+---------+-------+---------+-------------+ | 1 | PRIMARY | nodes | ALL | NULL | NULL | NULL | NULL | 2865521 | Using where | | 2 | DEPENDENT SUBQUERY | users | const | PRIMARY,users_display_name_idx | users_display_name_idx | 767 | const | 1 | Using index | | 2 | DEPENDENT SUBQUERY | changesets | eq_ref | PRIMARY | PRIMARY | 8 | func | 1 | Using where | +----+--------------------+------------+--------+--------------------------------+------------------------+---------+-------+---------+-------------+ 3 rows in set (0.00 sec) mysql-5.5.40 (osm) > SELECT ...; +-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+ | node_id | latitude | longitude | changeset_id | visible | timestamp | tile | version | +-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+ | 99890 | 515276425 | -1497621 | 552 | 1 | 2005-10-25 00:35:24 | 2062268512 | 1 | | 109174 | 515364532 | -1457329 | 1875 | 1 | 2006-01-20 00:01:27 | 2062268519 | 1 | | 276538 | 515324296 | -2094688 | 810 | 1 | 2005-11-25 21:42:53 | 2062267078 | 1 | | 442987 | 515449207 | -1275650 | 1941 | 1 | 2006-01-22 23:50:28 | 2062268628 | 1 | | 442988 | 515449741 | -1272860 | 1941 | 1 | 2006-01-22 23:50:29 | 2062268628 | 1 | | 498803 | 515438432 | -1269436 | 2171 | 1 | 2006-02-03 21:55:17 | 2062268628 | 1 | | 138212838 | 513010180 | -1699929 | 7757299 | 1 | 2011-04-03 18:14:14 | 2062220563 | 6 | +-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+ 7 rows in set (2.60 sec) This means that the subquery is executed almost 3 million times This means that the subquery is executed almost 3 million times
  • 164. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 166 Semijoin Optimization ● The only way to execute certain IN subqueries was to execute them with poor strategy – This forced rewriting of certain queries into JOINS or scalar subqueries, when possible ● There are now several additional options (many automatic): – Convert to a JOIN – Materialization (including index creation) – FirstMatch – LooseScan – Duplicate Weedout
  • 165. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 167 The Previous Query is Not a Problem in 5.6+/MariaDB 5.3+ mysql-8.0 (osm) > EXPLAIN SELECT * FROM nodes WHERE nodes.changeset_id IN ( SELECT changesets.id FROM change sets JOIN users ON changesets.user_id = users.id and users.display_name = 'Steve'); +----+-------------+------------+-------+--------------------------------+------------------------+---------+-------------------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+--------------------------------+------------------------+---------+-------------------+-------+-------------+ | 1 | SIMPLE | users | const | PRIMARY,users_display_name_idx | users_display_name_idx | 767 | const | 1 | Using index | | 1 | SIMPLE | changesets | ALL | PRIMARY | NULL | NULL | NULL | 70917 | Using where | | 1 | SIMPLE | nodes | ref | changeset_id | changeset_id | 8 | osm.changesets.id | 21 | NULL | +----+-------------+------------+-------+--------------------------------+------------------------+---------+-------------------+-------+-------------+ 3 rows in set (0.00 sec) mysql-8.0 (osm) > SELECT ...; +-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+ | node_id | latitude | longitude | changeset_id | visible | timestamp | tile | version | +-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+ | 99890 | 515276425 | -1497621 | 552 | 1 | 2005-10-25 00:35:24 | 2062268512 | 1 | ... | 138212838 | 513010180 | -1699929 | 7757299 | 1 | 2011-04-03 18:14:14 | 2062220563 | 6 | +-----------+-----------+-----------+--------------+---------+---------------------+------------+---------+ 7 rows in set (0.02 sec) Executed as a regular JOIN Executed as a regular JOIN
  • 166. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 168 First Match Strategy mysql-8.0 (osm) > EXPLAIN SELECT * FROM changesets WHERE id IN (SELECT changeset_id FROM nodes)G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: changesets partitions: NULL type: ALL possible_keys: PRIMARY key: NULL key_len: NULL ref: NULL rows: 70917 filtered: 100.00 Extra: NULL *************************** 2. row *************************** id: 1 select_type: SIMPLE table: nodes partitions: NULL type: ref possible_keys: changeset_id key: changeset_id key_len: 8 ref: osm.changesets.id rows: 33 filtered: 100.00 Extra: Using index; FirstMatch(changesets) 2 rows in set, 1 warning (0.00 sec) It is converting the ref into an eq_ref, shot- circuiting the execution It is converting the ref into an eq_ref, shot- circuiting the execution
  • 167. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 169 Enabling and disabling materialization, semijoin, etc mysql-8.0 (osm) > SHOW VARIABLES like 'optimizer_switch'G *************************** 1. row *************************** Variable_name: optimizer_switch Value: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_in tersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=o n,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materializat ion=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_m aterialization_cost_based=on,use_index_extensions=on,condition_fanout_filter =on,derived_merge=on 1 row in set (0.00 sec)
  • 168. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 170 FULLTEXT, GEODATA SEARCH AND JSON SUPPORT Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
  • 169. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 171 Fuzzy Search of “hotel/Grand Hotel/Hotel X/etc” ● “Typical” way to solve this: mysql (osm) > SELECT way_id as id, v FROM way_tags WHERE v like '%otel%'; 2125 rows in set (3.19 sec) Too slowToo slow
  • 170. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 172 Let’s Add an Index mysql (osm) > ALTER TABLE way_tags ADD INDEX(v); Query OK, 0 rows affected (23.96 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql (osm) > SELECT ...; 2125 rows in set (2.75 sec) mysql (osm) > EXPLAIN SELECT way_id as id, v FROM way_tags WHERE v like '%otel%'; +----+.+-------+---------------+------+---------+------+---------+----------+--------------------------+ | id |.| type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+.+-------+---------------+------+---------+------+---------+----------+--------------------------+ | 1 |.| index | NULL | v | 769 | NULL | 8964000 | 11.11 | Using where; Using index | +----+.+-------+---------------+------+---------+------+---------+----------+--------------------------+ 1 row in set, 1 warning (0.01 sec) Still slow, why?Still slow, why?
  • 171. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 173 Fulltext Index mysql (osm) > ALTER TABLE way_tags add FULLTEXT index(v); Query OK, 0 rows affected (53.53 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql (osm) > SELECT 'way' as type, way_id as id, v FROM way_tags WHERE MATCH(v) AGAINST ('+hotel*' IN BOOLEAN MODE); 1851 rows in set (0.04 sec) mysql (osm) > EXPLAIN …; +----+.+----------+---------------+------+---------+-----+------+----------+-----------------------------------+ | id |.| type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+.+----------+---------------+------+---------+-----+------+----------+-----------------------------------+ | 1 |.| fulltext | v | v | 0 | | 1 | 100.00 | Using where; Ft_hints: no_ranking | +----+.+----------+---------------+------+---------+-----+------+----------+-----------------------------------+ 1 row in set, 1 warning (0.00 sec) Some fields are useless for FULTEXT Some fields are useless for FULTEXT
  • 172. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 174 ‘Newer’ Fulltext Optimizations mysql-5.5 (osm) > EXPLAIN SELECT count(*) FROM way_tags_myisam WHERE MATCH(v) AGAINST('hotel'); +----+-------------+-----------------+----------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------------+----------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | way_tags_myisam | fulltext | v | v | 0 | | 1 | Using where | +----+-------------+-----------------+----------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec) mysql-5.5 (osm) > SHOW STATUS like 'Hand%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_commit | 0 | | Handler_delete | 0 | ... | Handler_read_first | 0 | | Handler_read_key | 0 | | Handler_read_last | 0 | | Handler_read_next | 425 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_next | 0 | ... | Handler_update | 0 | | Handler_write | 0 | +----------------------------+-------+ 16 rows in set (0.00 sec)
  • 173. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 175 Newer Fulltext Optimizations (cont.)mysql-8.0 (osm) > EXPLAIN SELECT count(*) FROM way_tags WHERE MATCH(v) AGAINST('hotel'); +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Select tables optimized away | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+ 1 row in set, 1 warning (0.00 sec) mysql-8.0 (osm) > SHOW STATUS like 'Hand%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_commit | 1 | | Handler_delete | 0 | ... | Handler_read_first | 0 | | Handler_read_key | 0 | | Handler_read_last | 0 | | Handler_read_next | 0 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_next | 0 | ... | Handler_update | 0 | | Handler_write | 0 | +----------------------------+-------+ 18 rows in set (0.00 sec) It’s counting directly from the FULLTEXT index It’s counting directly from the FULLTEXT index
  • 174. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 176 Open Issues and Limitations ● No postfix support (wildcards) ● Simple Ranking (and different from MyISAM) ● No stemming support ● Some multi-language limitations More on FULLTEXT InnoDB support: http://www.drdobbs.com/database/full-text-search-with-innodb/231902587
  • 175. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 177 Alternatives ● Apache Lucene – Solr – Elasticsearch ● Sphinx – SphinxSE
  • 176. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 178 Find the Closest Starbucks (Spatial Search)mysql (osm) > SET @lat:=50.11745057; SET @lon:= 8.62699463; mysql (osm) > SELECT n.node_id, n.longitude/10000000 as longitude, n.latitude/10000000 as latitude, sqrt(pow((latitude/10000000 - @lat) * 111231.29, 2) + pow((longitude/10000000 - @lon) * 71520.91, 2)) as `distance in metres` FROM nodes n JOIN node_tags n_t1 ON n.node_id = n_t1.node_id JOIN node_tags n_t2 ON n.node_id = n_t2.node_id WHERE n_t1.k = 'amenity' and n_t1.v = 'cafe' and n_t2.k = 'name' and n_t2.v like 'Starbucks%' ORDER BY `distance in metres` ASC LIMIT 1; +------------+-----------+----------+--------------------+ | node_id | longitude | latitude | distance in metres | +------------+-----------+----------+--------------------+ | 3136353405 | 8.6523 | 50.1093 | 2023.6465372574328 | +------------+-----------+----------+--------------------+ 1 row in set, 36 warnings (0.81 sec) You are hereYou are here
  • 177. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 179 This Query is Slow mysql [osm]> EXPLAIN SELECT ...G **************** 1. row *************** Id: 1 select_type: SIMPLE table: n_t1 type: ALL possible_keys: PRIMARY key: NULL key_len: NULL ref: NULL rows: 2210950 Extra: Using where; Using temporary; Using filesort *********************** 2. row *********************** id: 1 select_type: SIMPLE table: n_t2 type: ref possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: osm.n_t1.node_id rows: 1 Extra: Using where *********************** 3. row *********************** id: 1 select_type: SIMPLE table: n type: ref possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: osm.n_t1.node_id rows: 1 Extra: 3 rows in set (0.00 sec)
  • 178. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 180 Can We Optimize it? • We could add a bounding box: mysql (osm) > ALTER TABLE nodes ADD INDEX (latitude, longitude); mysql (osm) > SELECT n.node_id, n.longitude/10000000 as longitude, n.latitude/10000000 as latitude, sqrt(pow((latitude/10000000 - @lat) * 111231.29, 2) + pow((longitude/10000000 - @lon) * 71520.91, 2)) as `distance in metres` FROM nodes n JOIN node_tags n_t1 ON n.node_id = n_t1.node_id JOIN node_tags n_t2 ON n.node_id = n_t2.node_id WHERE n_t1.k = 'amenity' and n_t1.v = 'cafe' and n_t2.k = 'name' and n_t2.v like 'Starbucks%' and n.latitude BETWEEN ((@lat - 1000/111231.29) * 10000000) AND ((@lat + 1000/111231.29) * 10000000) and n.longitude BETWEEN ((@lon - 1000/71520.91) * 10000000) AND ((@lon + 1000/71520.91) * 10000000) ORDER BY `distance in metres` ASC LIMIT 1; This is not a square, only an approximation This is not a square, only an approximation
  • 179. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 181 We Create an Index… and Force It mysql-8.0 (osm) > EXPLAIN SELECT ...; +----+-------------+-------+------------+------+----------+---------+------+------------------+---------+----------+------------------+ | id | select_type | table | partitions | type | possible | key | key_ | ref | rows | filtered | Extra | | | | | | | _keys | | len | | | | | +----+-------------+-------+------------+------+----------+---------+------+------------------+---------+----------+------------------+ | 1 | SIMPLE | n_t1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 2210950 | 0.00 | Using where; | | | | | | | | | | | | | Using temporary; | | | | | | | | | | | | | Using filesort | | 1 | SIMPLE | n | NULL | ref | PRIMARY, | PRIMARY | 8 | osm.n_t1.node_id | 1 | 5.00 | Using where | | | | | | | latitude | | | | | | | | 1 | SIMPLE | n_t2 | NULL | ref | PRIMARY | PRIMARY | 8 | osm.n_t1.node_id | 1 | 1.41 | Using where | +----+-------------+-------+------------+------+----------+---------+------+------------------+---------+----------+------------------+ 3 rows in set, 1 warning (0.00 sec) mysql-8.0 (osm) > EXPLAIN SELECT ... FROM nodes n FORCE INDEX(latitude) ...; +----+-------------+-------+------------+-------+----------+----------+------+---------------+--------+----------+------------------+ | id | select_type | table | partitions | type | possible | key | key_ | ref | rows | filtered | Extra | | | | | | | _keys | | len | | | | | +----+-------------+-------+------------+-------+----------+----------+------+---------------+--------+----------+------------------+ | 1 | SIMPLE | n | NULL | range | latitude | latitude | 8 | NULL | 949370 | 11.11 | Using where; | | | | | | | | | | | | | Using index; | | | | | | | | | | | | | Using filesort | | 1 | SIMPLE | n_t1 | NULL | ref | PRIMARY | PRIMARY | 8 | osm.n.node_id | 1 | 1.41 | Using where | | 1 | SIMPLE | n_t2 | NULL | ref | PRIMARY | PRIMARY | 8 | osm.n.node_id | 1 | 1.41 | Using where | +----+-------------+-------+------------+-------+----------+----------+------+---------------+--------+----------+------------------+ 3 rows in set, 1 warning (0.00 sec) Mysql-8.0 (osm) > SELECT ... FROM nodes n FORCE INDEX(latitude) ...; 0 rows in set (0.26 sec) Still many rows are examined Still many rows are examined Most of the gain comes from the covering index, not the filtering Most of the gain comes from the covering index, not the filtering MySQL ignores the newly created index, why? MySQL ignores the newly created index, why? Performance improvement is not great Performance improvement is not great
  • 180. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 182 Multiple Range Scans Cannot Be Optimized with BTREE Indexes ● We need quadtrees or R-TREE Indexes for indexing in multiple dimensions – The latter are implemented in MySQL with the name “SPATIAL indexes”, as they only apply to GIS types ● Spatial indexing is available on MySQL since 5.7.5
  • 181. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 183 Creating a Spatial Index mysql (osm) > ALTER TABLE nodes ADD COLUMN coord GEOMETRY NOT NULL SRID 4326; Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql (osm) > UPDATE nodes SET coord = point(longitude/10000000, latitude/10000000); Query OK, 20216490 rows affected (7 min 10.28 sec) Rows matched: 20216490  Changed: 20216490  Warnings: 0 mysql (osm) > ALTER TABLE nodes add SPATIAL index(coord); Query OK, 0 rows affected (1 hour 7 min 13.29 sec) Records: 0 Duplicates: 0 Warnings: 0 This is new since 5.7 This is new since 5.7 Instant, yay! (or create it VIRTUAL) Instant, yay! (or create it VIRTUAL)
  • 182. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 184 New Query mysql> SET @area := ST_Envelope(linestring(POINT(@lon - 500/71520.91, @lat - 500/111231.29), POINT(@lon + 500/71520.91, @lat + 500/111231.29))); mysql> SELECT n.node_id, x(n.coord) as longitude, y(n.coord) as latitude, st_distance(POINT(@lon, @lat), coord) as distance FROM nodes n JOIN node_tags n_t1 ON n.node_id = n_t1.node_id JOIN node_tags n_t2 ON n.node_id = n_t2.node_id WHERE n_t1.k = 'amenity' and n_t1.v = 'cafe' and n_t2.k = 'name' and n_t2.v like 'Starbucks%' and st_within(coord, @area) ORDER BY st_distance(POINT(@lon, @lat), coord) ASC LIMIT 1; We can use any shape we want thanks to 5.6 improvements We can use any shape we want thanks to 5.6 improvements Also substitute functions like Envelope with ST_Envelope since 5.7 Also substitute functions like Envelope with ST_Envelope since 5.7
  • 183. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 185 Better Performance mysql (osm) > SELECT ...; +-----------+------------+------------+-----------------------+ | node_id | longitude | latitude | distance | +-----------+------------+------------+-----------------------+ | XXXXXXXXX | XXXXXXXXXX | XXXXXXXXXX | 0.0014631428672541478 | +-----------+------------+------------+-----------------------+ Empty set (0.11 sec) mysql (osm) > EXPLAIN SELECT ...; +----+--------+-------+-------+-------+----------+---------+------+---------+------+----------+----------------+ | id | select | table | parti | type | possible | key | key | ref | rows | filtered | Extra | | | _type | | tions | | _keys | | _len | | | | | +----+--------+-------+-------+-------+----------+---------+------+---------+------+----------+----------------+ | 1 | SIMPLE | n | NULL | range | PRIMARY | coord | 34 | NULL | 2 | 100.00 | Using where; | | | | | | | ,coord | | | | | | Using filesort | | 1 | SIMPLE | n_t1 | NULL | ref | PRIMARY | PRIMARY | 8 | osm.n. | 3 | 1.41 | Using where | | | | | | | | | | node_id | | | | | 1 | SIMPLE | n_t2 | NULL | ref | PRIMARY | PRIMARY | 8 | osm.n. | 3 | 1.41 | Using where | | | | | | | | | | node_id | | | | +----+--------+-------+-------+-------+----------+---------+------+---------+------+---------------------------+ 3 rows in set (0.00 sec) This field used to be almost useless (wait for it) This field used to be almost useless (wait for it)
  • 184. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 186 Better Filtering Not using the index: +----------------------------+--------+ | Variable_name | Value | +----------------------------+--------+ | Handler_commit | 1 | | Handler_delete | 0 | | Handler_discover | 0 | | Handler_external_lock | 6 | | Handler_mrr_init | 0 | | Handler_prepare | 0 | | Handler_read_first | 1 | | Handler_read_key | 1914 | | Handler_read_last | 0 | | Handler_read_next | 1954 | | Handler_read_prev | 0 | | Handler_read_rnd | 1 | | Handler_read_rnd_next | 833426 | | Handler_rollback | 0 | | Handler_savepoint | 0 | | Handler_savepoint_rollback | 0 | | Handler_update | 0 | | Handler_write | 1 | +----------------------------+--------+ 18 rows in set (0.00 sec) mysql-8.0 (osm) > SHOW STATUS LIKE 'Hand%'; Using the BTREE index: +----------------------------+--------+ | Variable_name | Value | +----------------------------+--------+ | Handler_commit | 1 | | Handler_delete | 0 | | Handler_discover | 0 | | Handler_external_lock | 6 | | Handler_mrr_init | 0 | | Handler_prepare | 0 | | Handler_read_first | 0 | | Handler_read_key | 274 | | Handler_read_last | 0 | | Handler_read_next | 246540 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | | Handler_savepoint | 0 | | Handler_savepoint_rollback | 0 | | Handler_update | 0 | | Handler_write | 0 | +----------------------------+--------+ 18 rows in set (0.00 sec) Using the SPATIAL index: +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_commit | 1 | | Handler_delete | 0 | | Handler_discover | 0 | | Handler_external_lock | 6 | | Handler_mrr_init | 0 | | Handler_prepare | 0 | | Handler_read_first | 0 | | Handler_read_key | 522 | | Handler_read_last | 0 | | Handler_read_next | 5254 | | Handler_read_prev | 0 | | Handler_read_rnd | 259 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | | Handler_savepoint | 0 | | Handler_savepoint_rollback | 0 | | Handler_update | 0 | | Handler_write | 0 | +----------------------------+-------+ 18 rows in set (0.00 sec)
  • 185. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 187 Geohash Functions mysql (osm) > SELECT ST_GeoHash(@lon, @lat, 10); +----------------------------+ | ST_GeoHash(@lon, @lat, 10) | +----------------------------+ | u0yjh79gr9 | +----------------------------+ 1 row in set (0.00 sec) • Useful to index coordinates with a BTREE – It could be specially useful combined with indexed STORED columns (emulating quadtrees) More on Geohashing: http://mysqlserverteam.com/geohash-functions/
  • 186. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 188 GeoJSON Functions mysql (osm) > SELECT nm.v, ST_AsGeoJson(n.coord) FROM node_tags n_t JOIN nodes n USING (node_id, version) JOIN node_tags nm USING (node_id, version) WHERE n_t.k='tourism' AND n_t.v='attraction' AND nm.k='name'; +-------------------------+------------------------------------------------------------+ | v | ST_AsGeoJson(n.coord) | +-------------------------+------------------------------------------------------------+ | Schießbuckel | {"type": "Point", "coordinates": [8.4938556, 49.6351754]} | | AKW Informationszentrum | {"type": "Point", "coordinates": [8.4175585, 49.7060227]} | | Burg Gleiberg | {"type": "Point", "coordinates": [8.6344314, 50.6150329]} | | Brüderkirche | {"type": "Point", "coordinates": [9.5042975, 51.3149351]} | | Römer | {"type": "Point", "coordinates": [8.6816587, 50.1104684]} | . .
  • 187. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 189 GeoJSON Functions (cont.) $ mysql osm -B -e "SET NAMES utf8; SET SESSION group_concat_max_len = 10000; SELECT CONCAT('{"type":"FeatureCollection", "features":[ ', GROUP_CONCAT(CONCAT('{"type":"Feature", "geometry":', ST_AsGeoJson(n.coord), ',"properties":{"name":"',nm.v,'"}}')), ' ]}') FROM node_tags n_t JOIN nodes n USING (node_id, version) JOIN node_tags nm USING (node_id, version) WHERE n_t.k='tourism' and n_t.v='attraction' AND nm.k='name'" http://geojsonlint.com/
  • 188. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 190 Older Issues • Before MySQL 8.0, SRID could be set and retrieved, but all operations were done in squared euclidean coordinates: mysql-5.7.5 (osm) > SET @p1 := GeomFromText('POINT(8 50)', 4326); Query OK, 0 rows affected (0.00 sec) mysql-5.7.5 (osm) > SET @p2 := GeomFromText('POINT(7 50)', 4326); Query OK, 0 rows affected (0.00 sec) mysql-5.7.5 (osm) > SET @p3 := GeomFromText('POINT(8 51)', 4326); Query OK, 0 rows affected (0.00 sec) mysql-5.7.5 (osm) > SELECT srid(@p1); +-----------+ | srid(@p1) | +-----------+ | 4326 | +-----------+ 1 row in set (0.00 sec) mysql-5.7.5 (osm) > SELECT st_distance(@p1, @p2); +-----------------------+ | st_distance(@p1, @p2) | +-----------------------+ | 1 | +-----------------------+ 1 row in set (0.00 sec) mysql-5.7.5 (osm) > SELECT st_distance(@p1, @p3); +-----------------------+ | st_distance(@p1, @p3) | +-----------------------+ | 1 | +-----------------------+ 1 row in set (0.00 sec)
  • 189. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 191 New 8.0 SRID Support ! mysql-8.0 (osm) > SET @p1 := ST_GeomFromText('POINT(8 50)', 4326); Query OK, 0 rows affected (0.00 sec) mysql-8.0 (osm) > SET @p2 := ST_GeomFromText('POINT(7 50)', 4326); Query OK, 0 rows affected (0.00 sec) mysql-8.0 (osm) > SET @p3 := ST_GeomFromText('POINT(8 51)', 4326); Query OK, 0 rows affected (0.00 sec) mysql-8.0 (osm) > SELECT ST_srid(@p1); +--------------+ | ST_srid(@p1) | +--------------+ | 4326 | +--------------+ 1 row in set (0.00 sec) mysql-8.0 (osm) > SELECT st_distance(@p1, @p2); +-----------------------+ | st_distance(@p1, @p2) | +-----------------------+ | 110243.2689139409 | +-----------------------+ 1 row in set (0.00 sec) mysql-8.0 (osm) > SELECT st_distance(@p1, @p3); +-----------------------+ | st_distance(@p1, @p3) | +-----------------------+ | 110592.12831394037 | +-----------------------+ 1 row in set (0.01 sec) Answers in meters- The world is no longer flat! Answers in meters- The world is no longer flat! I expected something around 71-72 Km, did I do something wrong? I expected something around 71-72 Km, did I do something wrong?
  • 190. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 192 JSON Native Data Type ● Since 5.7.8, MySQL allows columns defined with the JSON data type: mysql-8.0 > CREATE TABLE json_test(id int PRIMARY KEY auto_increment, content JSON); Query OK, 0 rows affected (0.03 sec) mysql-8.0 > INSERT INTO json_test (content) VALUES ('{"type": "correct_json"}'); Query OK, 1 row affected (0.00 sec) mysql-8.0 > INSERT INTO json_test (content) VALUES ('{"type": "incorrect_json}'); ERROR 3140 (22032): Invalid JSON text: "Missing a closing quotation mark in string." at position 24 in value (or column) '{"type": "incorrect_json}'. They get validated on insert They get validated on insert MariaDB supports it only as a BLOB/LONGT EXT alias MariaDB supports it only as a BLOB/LONGT EXT alias
  • 191. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 193 JSON functions ● MySQL includes almost all functions to manipulate JSON that you may think of: – Validation test: JSON_TYPE – Object creation: JSON_ARRAY, JSON_MERGE, ... – Searching: JSON_EXTRACT – Modifying: JSON_SET, JSON_INSERT, ...
  • 192. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 194 Indexing JSON ● JSON Columns cannot be indexed: mysql [localhost] {msandbox} (test) > ALTER TABLE json_test ADD INDEX(content); ERROR 3152 (42000): JSON column 'content' cannot be used in key specification. ● However, they can be compared with regular fields and use indexes thanks to virtual columns
  • 193. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 195 CONCLUSIONS Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics
  • 194. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 196 8.0/10.3 Recently released • They are mature enough to be used in production • Latest release doesn’t have as many new features related to query optimization – But they have a lot of other features and quality of life improvements – They also fix long outstanding query performance bugs – MariaDB also catches up with MySQL in some areas
  • 195. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 197 MySQL 8.0 New Features • MySQL 5.6 seemed Percona Server-inspired • MySQL 5.7 seemed MariaDB-inspired • MySQL 8.0 seems Galera and Oracle DB-inspired – Competition is always good for consumer
  • 196. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 198 Modern SQL is now part of MySQL! ● Window functions, recursive CTEs: WITH RECURSIVE cte (cl_from, cl_type) AS ( SELECT cl_from, cl_type FROM categorylinks WHERE cl_to = 'Database_management_systems' -- starting category UNION SELECT categorylinks.cl_from, categorylinks.cl_type FROM cte JOIN page ON cl_from = page_id JOIN categorylinks ON page_title = cl_to WHERE cte.cl_type = 'subcat' -- subcat addition on each iteration ) SELECT page_title FROM cte JOIN page ON cl_from = page_id WHERE page_namespace = 0 ORDER BY page_title; -- printing only articles in the end, ordered by title Scan Wikipedia categories, page links recursively! Scan Wikipedia categories, page links recursively!
  • 197. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 199 Many Optimizer Advantages Have to Be Manually Enabled ● Modifying on a per-query basis: SET optimizer_switch='batched_key_access=on'; SET join_cache_level=8; # for MariaDB in order to take advantage of them, making some of features useless unless you are fine- tuning – I expect that to change in the future
  • 198. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 200 I Herby Declare MyISAM as Dead • All major MyISAM-only features are now on MySQL 5.7/8.0 – FULLTEXT – GIS – Transportable tables • Previous blockers such as grant tables (new InnoDB data dictionary) and temporary tables are now in InnoDB format
  • 199. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 201 Benchmarks ● Do not trust first party benchmarks – In fact, do not trust 3rd party benchmarks either ● Only care about the performance of your application running on your hardware
  • 200. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 202 Q&A
  • 201. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 203 Not to Miss ● Operations track: TLS for MySQL at Large Scale: How we do relational data on- the-wire encryption at the Wikimedia Foundation ● Do you want to do query optimization for a website with 20 Billion views per month? https://wikimediafoundation.org/about/jobs/
  • 202. © 2018 Jaime Crespo. http://jynus.com. License: CC-BY-SA-4.0 Query Optimization with MySQL 8.0 and MariaDB 10.3: The Basics 204 Thank You for Attending! ● Do not forget, after the session finishes, to please login with your Percona Live app and “Rate This Session” ● Special thanks to in order by rand() to: Ariel Glenn, Manuel Arostegui, Morgan Tocker, Sean Pringle, David Hildebrandt, Bill Karwin, Domas Mituzas, Mark Callaghan, Shlomi Noach, Valerii Kravchuk, Miguel Ángel Nieto, Dimitri Kravtchuk, Olav Sandstå and the whole Wikimedia Team, and all people at the MariaDB, Percona and MySQL/Oracle teams, and the Percona Live Organization and Sponsors