SlideShare ist ein Scribd-Unternehmen logo
1 von 97
Downloaden Sie, um offline zu lesen
Improving MySQL/MariaDB
query performance
through optimizer tuning
Sergey Petrunya
Timour Katchaounov
{psergey,timour}@montyprogram.com
What is an optimizer
and why do I need to tune it




                               2   03:14:28 PM
Why does one need optimizer tuning

•   Database performance is affected by many factors



                               •   One of them is the
                                   query optimizer
                                   (and query
                                   executor, together
                                   query processor)




                                              3     03:14:28 PM
What is a query optimizer ?

•   Converts SQL into execution instructions
•   Like GPS gives driving directions from A to B




                +----+--------------------+----------+--------+-----------------------------------+--------------+----
                | id | select_type        | table    | type   | possible_keys                     | key          | key
                +----+--------------------+----------+--------+-----------------------------------+--------------+----
                | 1 | PRIMARY             | part     | ALL    | PRIMARY                           | NULL         | NUL
                | 1 | PRIMARY             | partsupp | ref    | PRIMARY,i_ps_partkey,i_ps_suppkey | i_ps_partkey | 4
                | 1 | PRIMARY             | supplier | eq_ref | PRIMARY,i_s_nationkey             | PRIMARY      | 4
                | 1 | PRIMARY             | nation   | eq_ref | PRIMARY,i_n_regionkey             | PRIMARY      | 4
                | 1 | PRIMARY             | region   | ALL    | PRIMARY                           | NULL         | NUL
                | 2 | DEPENDENT SUBQUERY | partsupp | ref     | PRIMARY,i_ps_partkey,i_ps_suppkey | i_ps_partkey | 4
                | 2 | DEPENDENT SUBQUERY | supplier | eq_ref | PRIMARY,i_s_nationkey              | PRIMARY      | 4
                | 2 | DEPENDENT SUBQUERY | nation    | eq_ref | PRIMARY,i_n_regionkey             | PRIMARY      | 4
                | 2 | DEPENDENT SUBQUERY | region    | eq_ref | PRIMARY                           | PRIMARY      | 4
                +----+--------------------+----------+--------+-----------------------------------+--------------+----




                                                                                         4              03:14:28 PM
Can the optimizer work poorly?


Yes, just like your GPS
It can produce a sub-optimal query
plan (compare to GPS's directions)
Unneeded CPU/IO usage in the
database is the same extra mileage
How can one tell if they have a
problem in query optimizer?
•   Like with the GPS:
    •   Knowledge of possible routes   This is what this
                                       tutorial is about
    •   Common sense
                                               5     03:14:28 PM
Start from a database performance problem


•   Database response time is too high
    •   or the server denies connections altogether
        •   because running queries have occupied all
            @@max_connections threads.
•   Monitoring (cacti, etc) shows high CPU/
    disk utilization
•   Is this an optimizer problem?
    •   Maybe.


                                                   6    03:14:29 PM
Signs that it's caused by the optimizer

•   Only certain kinds of queries are affected
    •   Unlike concurrency/disk/binlog problems that
        cause slowdown for all queries
•   These queries can be caught with
    •   Slow query log
        •   pt-query-digest

    •   SHOW PROCESSLIST
    •   SHOW PROFILE

                                             7    03:14:29 PM
Optimizer problems in Slow query log

•   Percona server/MariaDB:
    --log_slow_verbosity=query_plan
# Thread_id: 1 Schema: dbt3sf10 QC_hit: No
# Query_time: 2.452373 Lock_time: 0.000113 Rows_sent: 0 Rows_examined: 1500000
# Full_scan: Yes Full_join: No Tmp_table: No Tmp_table_on_disk: No
# Filesort: No Filesort_on_disk: No Merge_passes: 0
SET timestamp=1333385770;
select * from customer where c_acctbal < -1000;

•   Look for big Query_time (>> Lock_time)
•   Look for Rows_examined >> Rows_sent
    •   Although there are “legitimate” cases for this
•   Query_plan members (violet), if present, also show
    *possible* problem areas
                                                                 8        03:14:29 PM
SHOW PROCESSLIST;

MariaDB [dbt3sf10]> show processlist;
+----+------+-----------+----------+---------+------+---------------+--------
| Id | User | Host      | db       | Command | Time | State         | Info
+----+------+-----------+----------+---------+------+---------------+--------
| 1 | root | localhost | dbt3sf10 | Query    | 176 | Sending data | select
| 2 | root | localhost | dbt3sf10 | Query    |    0 | NULL          | show pr
| 3 | root | localhost | dbt3sf10 | Query    |   19 | freeing items | insert
| 4 | root | localhost | dbt3sf10 | Sleep    |   77 |               | NULL
| 5 | root | localhost | dbt3sf10 | Query    |    4 | Updating      | update
| 6 | root | localhost | dbt3sf10 | Query    |    5 | statistics    | select


•   States to look for:
    •   optimizing
    •   executing
    •   Statistics
    •   Sending data

                                                                  9        03:14:29 PM
SHOW PROFILE

•   Uses the same set of states
•   Shows where exactly the query has spent time
    mysql> show profile for query 1;
    +--------------------+-----------+
    | Status             | Duration |
    +--------------------+-----------+
    | starting           | 0.000097 |
    | Opening tables     | 0.000038 |
                                         Phases that can occupy lots of time:
    | System lock        | 0.000014 |
    | Table lock         | 0.000019 |    Storage engine: transaction start, etc
    | init               | 0.000027 |
    | optimizing         | 0.000029 |    Optimization (search for query plan)
    | statistics         | 0.000036 |    Optimization and sometimes InnoDB locking
    | preparing          | 0.000023 |
    | executing          | 0.000014 |    Execution (of the query plan)
    | Sending data       | 18.501940 |
    | end                | 0.000015 |
    | query end          | 0.000006 |
    | freeing items      | 0.000029 |
    | logging slow query | 0.000004 |    Storage engine: transaction commit,
    | logging slow query | 0.000050 |    binary logging
    | cleaning up        | 0.000006 |
                                                                   10         03:14:29 PM
    +--------------------+-----------+
Global observations summary

•   A query [pattern] that
    •   Spends a lot of time in optimization/execution
        phase
    •   Examines many more records than you think
        it should
    •   Uses Full_scan, Full_join, etc
    •   .. or all of the above
•   Is a candidate for further investigation


                                              11   03:14:29 PM
Reasons why a query is “heavy”

•   Some queries are
    inherently hard to
    compute

•   Some get a plan which looks
    good but turns out bad due
    to circumstances that were
    not accounted for

•   And some are just bad query plan
    choices

                                       12   03:14:30 PM
Know thy optimizer

•   How to tell which case you're dealing with?
     •   Need to know “the distance”
         •   The lower bound of amount of effort
             one needs to expend to arrive at the
             destination
         •   Easy in geography, not so easy in SQL
     •   Need to know “the roads”
         •   Available table access strategies
         •   How they are composed into query plans



                                                      13   03:14:30 PM
Optimizer troubleshooting
From simple to complex queries




                                 14   03:14:30 PM
Let's take a simple, slow query
      select * from orders
      where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506';
+----+-------------+--------+------+---------------+------+---------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows        | Extra       |
+----+-------------+--------+------+---------------+------+---------+------+----------+-------------+
| 1 | SIMPLE       | orders | ALL | NULL           | NULL | NULL    | NULL | 15084733 | Using where |
+----+-------------+--------+------+---------------+------+---------+------+----------+-------------+

# Thread_id: 2 Schema: dbt3sf10 QC_hit: No
# Query_time: 41.129178 Lock_time: 0.000174 Rows_sent: 2 Rows_examined: 15000000
# Full_scan: Yes Full_join: No Tmp_table: No Tmp_table_on_disk: No
# Filesort: No Filesort_on_disk: No Merge_passes: 0
use dbt3sf10;
SET timestamp=1333432984;
select * from orders where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506';

  •   Full scan examined lots of rows
  •   But only a few (two) of them “have
      contributed to the result set”
       •    It was unnecessary to read the rest
  •   Query plan is poor, it seems.
                                                                                                        15   03:14:31 PM
Status variables
MariaDB [dbt3sf10]> flush status;
Query OK, 0 rows affected (0.00 sec)

MariaDB [dbt3sf10]> select * from orders where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506';
...

MariaDB [dbt3sf10]> show status like 'Handler%';
+----------------------------+----------+
| Variable_name              | Value    |
+----------------------------+----------+
| Handler_commit             | 1        |
| Handler_delete             | 0        |
| Handler_discover           | 0        |
| Handler_icp_attempts       | 0        |
| Handler_icp_match          | 0        |
| Handler_mrr_init           | 0        |
| Handler_mrr_key_refills    | 0        |
| Handler_mrr_rowid_refills | 0         |
| Handler_prepare            | 0        |
| Handler_read_first         | 0        |
| Handler_read_key           | 0        |
| Handler_read_next          | 0        |
| Handler_read_prev          | 0        |
| Handler_read_rnd           | 0        |
| Handler_read_rnd_deleted   | 0        |          Indeed, 15M sequential reads
| Handler_read_rnd_next      | 15000001 |
| Handler_rollback           | 0        |
| Handler_savepoint          | 0        |
| Handler_savepoint_rollback | 0        |
| Handler_tmp_update         | 0        |
| Handler_tmp_write          | 0        |
| Handler_update             | 0        |
| Handler_write              | 0        |
+----------------------------+----------+
23 rows in set (0.01 sec)
                                                                                           16            03:14:31 PM
With index, we'll read fewer rows

alter table orders add key i_o_orderdate (o_orderdate);
select * from orders
where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506';
+----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key            | key_len | ref   | rows | Extra       |
+----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+
| 1 | SIMPLE       | orders | ref | i_o_orderdate | i_o_orderdate | 4        | const | 6303 | Using where |
+----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+


 •   The index allows to only read
     records with i_o_orderdate='Clerk..'
      •   (At the cost of having to read
          index entries also)
 •   Still not perfect: we're reading
     records that have the wrong
     o_orderDate
 •   Let's check how it executes:
                                                                                              17          03:14:31 PM
Status variables
MariaDB [dbt3sf10]> flush status;

MariaDB [dbt3sf10]> select * from orders where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506';
... (0.39 sec)

MariaDB [dbt3sf10]> show status like 'Handler%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_icp_attempts       | 0     |
| Handler_icp_match          | 0     |
| Handler_mrr_init           | 0     |
| Handler_mrr_key_refills    | 0     |
| Handler_mrr_rowid_refills | 0      |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key           | 1     |             1 index lookup
| Handler_read_next
| Handler_read_prev
                             | 6122 |
                             | 0     |
                                                   6K forward index reads
| Handler_read_rnd           | 0     |
| Handler_read_rnd_deleted   | 0     |
| Handler_read_rnd_next      | 0     |
| Handler_rollback           | 0     |
| Handler_savepoint          | 0     |
| Handler_savepoint_rollback | 0     |
| Handler_tmp_update         | 0     |
| Handler_tmp_write          | 0     |
| Handler_update             | 0     |
| Handler_write              | 0     |
+----------------------------+-------+
23 rows in set (0.00 sec)

                                                                                              18         03:14:31 PM
Can have an even better index

alter table orders add key i_o_date_clerk (o_orderdate, o_clerk);
select * from orders
where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506';
+--+-----------+------+----+----------------------------+--------------+-------+-----------+----+---------------------+
|id|select_type|table |type|possible_keys               |key           |key_len|ref        |rows|Extra                |
+--+-----------+------+----+----------------------------+--------------+-------+-----------+----+---------------------+
| 1|SIMPLE     |orders|ref |i_o_orderdate,i_o_date_clerk|i_o_date_clerk|20     |const,const|   1|Using index condition|
+--+-----------+------+----+----------------------------+--------------+-------+-----------+----+---------------------+


    •   Added a wide index covering both
        columns
    •   Now, only records that match the
        whole WHERE will be read
         •    Perfect!




                                                                                                   19           03:14:31 PM
Status variables
MariaDB [dbt3sf10]> select * from orders   where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506';
... (0.00 sec)

MariaDB [dbt3sf10]> show status like 'Handler%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_icp_attempts       | 0     |
| Handler_icp_match          | 0     |
| Handler_mrr_init           | 0     |
| Handler_mrr_key_refills    | 0     |
| Handler_mrr_rowid_refills | 0      |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key
| Handler_read_next
                             | 1
                             | 0
                                     |
                                     |
                                                    1 index lookup, and that's it
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 0     |
| Handler_read_rnd_deleted   | 0     |
| Handler_read_rnd_next      | 0     |
| Handler_rollback           | 0     |
| Handler_savepoint          | 0     |
| Handler_savepoint_rollback | 0     |
| Handler_tmp_update         | 0     |
| Handler_tmp_write
| Handler_update
                             | 0
                             | 0
                                     |
                                     |
                                                      •   Let's try to generalize..
| Handler_write              | 0     |
+----------------------------+-------+
23 rows in set (0.00 sec)



                                                                                           20          03:14:31 PM
Index fundamentals

ALTER TABLE tbl ADD INDEX(column1, column2, column3)
On the physical level,          On a logical level: an
it's a B-TREE                   ordered directory




                                             21      03:14:32 PM
Querying data through index: 'ref'

 alter table orders add index(o_orderDATE, o_shippriority, o_custkey)
                                                o_orderDATE o_shipprio o_custkey
•   ref access can use any prefix:                2011-11-01     1        10
                                                  2011-11-02     1        10
o_orderDATE='2011-11-02'                          2011-11-02     1        10
                                                  2011-11-02     1        11
                                                  2011-11-02     2        10
                                                  2011-11-03     2        10
o_orderDATE='2011-11-02' AND                      2011-11-03     2        11
  o_shippriority=2                                2011-11-04     3         9
                                                  2011-11-04     4        10
                                                  2011-11-05     3        11
o_orderDATE='2011-11-08' AND                      2011-11-06     1        10
                                                  2011-11-08     2         4
 o_shippriority=3 AND o_custkey= 4                2011-11-09     1        10

•   Not allowed:
    o_shipprioirity=2      o_shipprioirity=2
                                                                 22        03:14:32 PM
Ref(const) access summary
 •    Uses equalities to make index lookups
 •    #rows estimate is usually precise
 •    ANALYZE will not help
      EXPLAIN: type=ref, ref=const, key_len
+----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key            | key_len | ref   | rows | Extra       |
+----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+
| 1 | SIMPLE       | orders | ref | i_o_orderdate | i_o_orderdate | 4        | const | 6303 | Using where |
+----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+


  •   Status variables:
       •   1 x Handler_read_key
       •   N x Handler_read_next
  •   Estimates and control.. will talk later
                                                                                            23          03:14:32 PM
Range access




               24   03:14:32 PM
Range access

 •   Can use equality and non-equality comparisons
 •   Think of index as a directed axis.
 •   and “ranges” on it where the WHERE condition is TRUE:

… WHERE o_orderDATE > '2010-01-01'




… WHERE o_orderDATE > '2010-01-01' AND o_orderDATE < '2010-01-07'




                                                      25     03:14:32 PM
Range access (2)

•    Arbitrarily deep AND/OR formulas are supported
•    The optimizer will split out the “useful” parts of WHERE
•    And produce a list of disjoint ranges to be scanned:




•    “Useful” conditions compare key with constant:

           tbl.key>const               tbl.key=const
                                                             tbl.key BETWEEN c1 AND c2
                                   tbl.key IS NULL
    tbl.key LIKE 'foo%'

                           tbl.key IN (const1, const2 ...)

                                                                            26      03:14:33 PM
Range access for multi-part keys

•   Much more complex
•   Ranges over (keypart1, keypart2, …) tuple space
    •       Think of it as axis with sub-axis at each point:




        •   Range building rules:
             •   Number of ranges is a function of WHERE, not of data in the
                 table
             •   Do not use knowledge about domain density
                 (e.g. no knowledge like “there is no integers between
                  1 and 2”)
                                                                 27      03:14:33 PM
Multi-key-part range #1

•   Equality on 1st key part? Can put condition on 2nd key
    part into sub-axis
alter table orders add key i_o_date_clerk (o_orderdate, o_clerk);
select * from orders
where o_orderDATE IN ('2010-01-01', '2010-01-03') AND o_shippriority=1




                                                                28       03:14:33 PM
Multi-key-part range #2

•   This can cause big number of ranges. That's ok
select * from orders
where
 o_orderDATE IN ('2010-01-01', '2010-01-03') AND
 (o_shippriority =1 OR o_shippriority=2 OR o_shippriority=3)




                                                               29   03:14:33 PM
Multi-key-part range #3
•   Non-equality on 1st keypart:
select * from orders
where o_orderDATE >= '2010-01-01' AND o_shippriority>3




                                                30       03:14:33 PM
Multi-key-part range #3
•   Non-equality on 1st keypart:
select * from orders
where o_orderDATE >= '2010-01-01' AND o_shippriority>3
•   Can attach the second only to the edge “cell”:




                                                     31   03:14:34 PM
Multi-key-part range summary




•   keyparts for which queries have equalities should go first
•   multiple keyparts with non-equality ranges will not reduce the
    number of rows read
       e.g. t.keypart1 BETWEEN … AND t.keypart2 BETWEEN …
       won't take advantage of condition on keypart2
•   When keypartX is followed by other keyparts, then
      keypartX IN (1,... N) will work better than keypartX BETWEEN 1 AND N



                                                               32      03:14:34 PM
Can I look at the ranges it has constructed?

    •   Normally, no. But there is a last-resort method:
            •   Get a really small subset of your data onto separate server
            •   Get a debug build there. Start it with –debug
            •   Run your query with FORCE INDEX:
         explain select * from orders force index(i_o_datepriokey)
         where (o_orderDATE IN ('2010-02-01','2010-02-03') AND o_shippriority < 3) OR
                 (o_orderDATE >= '2010-03-10' AND o_shippriority < 4)
•       Go into mysqld.trace
        •       Grep for “query:” until you find the query
        •       Grep for “>print_quick”



                                                                              33        03:14:34 PM
Ranges in mysqld.trace

      mysqld.trace
      T@10   : | | query: explain select * from orders force index(i_o_datepriokey) where o_orderDATE
      IN ('2010-02-01','2010-02-03') AND o_shippriority < 3 OR (o_orderDATE > '2010-03-10' AND o
      _shippriority < 4)
      T@10   : | | >PROFILING::set_query_source
      T@10   : | | <PROFILING::set_query_source
      T@10   : | | >mysql_parse

      ...

      T@10   : | | | | | | | | | | >print_quick
      quick range select, key i_o_datepriokey, length: 9
        2010-02-01/NULL < X < 2010-02-01/3
        2010-02-03/NULL < X < 2010-02-03/3
        2010-03-10 < X
      other_keys: 0x0:


  •    EXPLAIN only shows key_len which is max # keyparts used

+----+-------------+--------+-------+-----------------+-----------------+---------+------+------+--
| id | select_type | table | type | possible_keys     | key             | key_len | ref | rows | E
+----+-------------+--------+-------+-----------------+-----------------+---------+------+------+--
| 1 | SIMPLE       | orders | range | i_o_datepriokey | i_o_datepriokey | 9       | NULL |    3 | U
+----+-------------+--------+-------+-----------------+-----------------+---------+------+------+--




                                                                                        34         03:14:34 PM
Range access estimates
+----+-------------+--------+-------+-----------------+-----------------+---------+------+------+--
| id | select_type | table | type | possible_keys     | key             | key_len | ref | rows | E
+----+-------------+--------+-------+-----------------+-----------------+---------+------+------+--
| 1 | SIMPLE       | orders | range | i_o_datepriokey | i_o_datepriokey | 9       | NULL |    3 | U
+----+-------------+--------+-------+-----------------+-----------------+---------+------+------+--

  •   Are obtained by doing dives into the index
       •   Dive at the start of the range
       •   Dive at the end
       •   Estimate the difference
  •   Usually are precise (not more than 2x wrong)
  •   Are not affected/do not need ANALYZE TABLE
  •   Cost you disk IO!
       •   Indexes that are never worth using will still be probed!
       •   Check your buffer pool + EXPLAINs to see if this happens!
            •   Quick fix: IGNORE INDEX (unusable_index)
                                                                                       35         03:14:34 PM
Range access: Status variables

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_icp_attempts       | 9     |
| Handler_icp_match          | 9     |
| Handler_mrr_init           | 0     |
| Handler_mrr_key_refills    | 0     |
| Handler_mrr_rowid_refills | 0      |
| Handler_prepare            | 0     |
| Handler_read_first
| Handler_read_key
                             | 0
                             | 9
                                     |
                                     |
                                         Increments
| Handler_read_next          | 9     |
| Handler_read_prev          | 0     |   - index lookup counter
| Handler_read_rnd           | 0     |
| Handler_read_rnd_deleted   | 0     |
| Handler_read_rnd_next      | 0     |   - walk-forward-in-index counter
| Handler_rollback           | 0     |
| Handler_savepoint          | 0     |
| Handler_savepoint_rollback | 0     |
| Handler_tmp_update         | 0     |
| Handler_tmp_write          | 0     |
| Handler_update             | 0     |
| Handler_write              | 0     |
+----------------------------+-------+
23 rows in set (0.05 sec)
                                                                    36     03:14:35 PM
Index-only scans




                   37   03:14:35 PM
Index-only scans

•   Besides lookups, indexes have another use

•   If the index has all
    columns you need, it
    is possible to avoid
    accessing table rows
    altogether
•   This is called “index
    only” scan
•   Can be applied to ref,
    and range accesses.

                                                38   03:14:35 PM
Index-only read example
 explain select sum(o_totalprice)
 from orders
 where o_orderdate between '1995-01-01' and '1995-01-07';

+--+-----------+------+-----+-------------+-------------+-------+----+----+---------------------+
|id|select_type|table |type |possible_keys|key          |key_len|ref |rows|Extra                |
+--+-----------+------+-----+-------------+-------------+-------+----+----+---------------------+
| 1|SIMPLE     |orders|range|i_o_orderdate|i_o_orderdate|4      |NULL|4358|Using index condition|
+--+-----------+------+-----+-------------+-------------+-------+----+----+---------------------+

 alter table orders add key o_date_price (o_orderDATE, o_totalprice);
 # re-run the EXPLAIN:


 alter table orders add key o_date_price (o_orderDATE, o_totalprice);
 explain select sum(o_totalprice) from orders where o_orderdate between '1995-01-01' and '1995-02-01';
 +--+-----------+------+-----+-------------+------------+-------+----+-----+------------------------+
 |id|select_type|table |type |possible_keys|key         |key_len|ref |rows |Extra                   |
 +--+-----------+------+-----+-------------+------------+-------+----+-----+------------------------+
 | 1|SIMPLE     |orders|range|...          |o_date_price|4      |NULL|39424|Using where; Using index|
 +--+-----------+------+-----+-------------+------------+-------+----+-----+------------------------+

  •   scale=1, in-mem: from
      0.2 to 0.1 sec

                                                                                      39            03:14:35 PM
Index-only reads and status variables

•   Surprisingly, no difference
     select sum(o_totalprice)                     select sum(o_totalprice)
     from orders use index(o_date_price)          from orders use index(i_o_orderdate)
     where o_orderdate between '1995-01-01' and   where o_orderdate between '1995-01-01' and
                                '1995-02-01';                                '1995-02-01';
     +----------------------------+-------+       +----------------------------+-------+
     | Variable_name               | Value |      | Variable_name               | Value |
     +----------------------------+-------+       +----------------------------+-------+
     | Handler_commit              | 1     |      | Handler_commit              | 1     |
     | Handler_delete              | 0     |      | Handler_delete              | 0     |
     | Handler_discover            | 0     |      | Handler_discover            | 0     |
     | Handler_icp_attempts        | 0     |      | Handler_icp_attempts        | 20137 |
     | Handler_icp_match           | 0     |      | Handler_icp_match           | 20137 |
     | Handler_mrr_init            | 0     |      | Handler_mrr_init            | 0     |
     | Handler_mrr_key_refills     | 0     |      | Handler_mrr_key_refills     | 0     |
     | Handler_mrr_rowid_refills | 0       |      | Handler_mrr_rowid_refills | 0       |
     | Handler_prepare             | 0     |      | Handler_prepare             | 0     |
     | Handler_read_first          | 0     |      | Handler_read_first          | 0     |
     | Handler_read_key            | 1     |      | Handler_read_key            | 1     |
     | Handler_read_next           | 20137 |      | Handler_read_next           | 20137 |
     | Handler_read_prev           | 0     |      | Handler_read_prev           | 0     |
     | Handler_read_rnd            | 0     |      | Handler_read_rnd            | 0     |
     | Handler_read_rnd_deleted    | 0     |      | Handler_read_rnd_deleted    | 0     |
     | Handler_read_rnd_next       | 0     |      | Handler_read_rnd_next       | 0     |
     | Handler_rollback            | 0     |      | Handler_rollback            | 0     |
     | Handler_savepoint           | 0     |      | Handler_savepoint           | 0     |
     | Handler_savepoint_rollback | 0      |      | Handler_savepoint_rollback | 0      |
     | Handler_tmp_update          | 0     |      | Handler_tmp_update          | 0     |
     | Handler_tmp_write           | 0     |      | Handler_tmp_write           | 0     |
     | Handler_update              | 0     |      | Handler_update              | 0     |
     | Handler_write               | 0     |      | Handler_write               | 0     |
     +----------------------------+-------+                                     40
                                                  +----------------------------+-------+   03:14:35 PM
Index-only reads and status variables

   •   Let's look at InnoDB “lower-level” counters
show status like 'Innodb%';
+-----------------------------------+--------+
                                                        •   These are global non FLUSHable
| Variable_name                     | Value |               counters, so we manually
+-----------------------------------+--------+
...                                                         calculate increment and show it
| Innodb_buffer_pool_read_requests | 413168 | +68929        on the right
| Innodb_buffer_pool_reads          | 10034 |
| Innodb_buffer_pool_wait_free      | 0      |
...                                                     •   _rows_read are the same, too
| Innodb_rows_inserted              | 0      |
| Innodb_rows_read
| Innodb_rows_updated
                                    | 181237 | +20137
                                    | 0      |
                                                        •   _buffer_pool_reads are different
+-----------------------------------+--------+
                                                        •   A bit sense-less counting
show status like 'Innodb%';
+-----------------------------------+--------+
                                                            •   Have plans to fix in
| Variable_name                     | Value |                   MariaDB.
+-----------------------------------+--------+
...
| Innodb_buffer_pool_read_requests | 422426 | +2566
| Innodb_buffer_pool_reads          | 10034 |
| Innodb_buffer_pool_wait_free      | 0      |
...
| Innodb_rows_inserted              | 0      |
| Innodb_rows_read                  | 241651 | +20138
| Innodb_rows_updated               | 0      |
+-----------------------------------+--------+
                                                                               41       03:14:35 PM
Index-only reads summary

•   Can be used with any index-based access methods
•   Allows to skip accessing table records
    •   Some savings for CPU-bound loads
    •   Huge savings for IO-bound loads
    •   Extra columns in the index a cost, though
         •   Index is bigger
         •   [Potentially] more frequently updated
•   EXPLAIN shows “Using index”
•   Counting in for Handler_xxx and Innodb_row_reads
    status variables
    •   Watch lower-level counters.
                                                     42   03:14:35 PM
Index Condition Pushdown
   MySQL 5.6+, MariaDB 5.3+




                              43   03:14:36 PM
Index Condition Pushdown idea

A non-index only read is a
two-step process:
1. Read index
2. Read record

3. Check the WHERE
  condition.




                             44   03:14:36 PM
Index Condition Pushdown idea

Index Condition Pushdown

1. Read index
2. Check condition on
   index columns
3. Read record
4. Check the WHERE
   condition




                            45   03:14:36 PM
Index condition pushdown example

     •    Start without ICP
         alter table orders add key i_o_clerk(o_clerk);
         select o_clerk, o_orderkey, o_shippriority
         from orders
         where o_clerk IN ('Clerk#000000001', 'Clerk#00000201' , 'Clerk#000003001') and
                 dayofweek(o_orderDATE)=1;
 # MariaDB 5.2.x/ MySQL 5.5.x
 +--+-----------+------+-----+-------------+---------+-------+----+----+-----------+
 |id|select_type|table |type |possible_keys|key      |key_len|ref |rows|Extra      |
 +--+-----------+------+-----+-------------+---------+-------+----+----+-----------+
 | 1|SIMPLE     |orders|range|i_o_clerk    |i_o_clerk|16     |NULL|2978|Using where|
 +--+-----------+------+-----+-------------+---------+-------+----+----+-----------+

# MariaDB 5.3+, MySQL 5.6+
+--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+
|id|select_type|table |type |possible_keys|key      |key_len|ref |rows|Extra                             |
+--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+
| 1|SIMPLE     |orders|range|i_o_clerk    |i_o_clerk|16     |NULL|2978|Using index condition; Using where|
+--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+




                                                                                        46         03:14:36 PM
Counters

    alter table orders add key i_o_clerk(o_clerk);
    select o_clerk, o_orderkey, o_shippriority
    from orders
    where o_clerk IN ('Clerk#000000001', 'Clerk#00000201' , 'Clerk#000003001') and
            dayofweek(o_orderDATE)=1;
# MariaDB 5.3+, MySQL 5.6+
+--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+
|id|select_type|table |type |possible_keys|key      |key_len|ref |rows|Extra                             |
+--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+
| 1|SIMPLE     |orders|range|i_o_clerk    |i_o_clerk|16     |NULL|2978|Using index condition; Using where|
+--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+
 +----------------------------+-------+
 | Variable_name              | Value |        MariaDB [(none)]> show status like 'Innodb%';
 +----------------------------+-------+        +----------------------------------+-------+
 | Handler_commit             | 1     |        | Variable_name                    | Value |
 | Handler_delete             | 0     |        +----------------------------------+-------+
 | Handler_discover           | 0     |        | Innodb_buffer_pool_read_requests | 32103 | + 24416
 | Handler_icp_attempts       | 2979 |         | Innodb_rows_read                 | 3406 | + 2979
 | Handler_icp_match          | 2979 |
 | Handler_mrr_init           | 0     |
 | Handler_mrr_key_refills    | 0     |
 | Handler_mrr_rowid_refills | 0      |
 | Handler_prepare            | 0     |
 | Handler_read_first         | 0     |
 | Handler_read_key           | 3     |
 | Handler_read_next          | 2979 |
 | Handler_read_prev          | 0     |
 | Handler_read_rnd           | 0     |
 ...                                                                                   47         03:14:36 PM
Index condition pushdown example

  •    Now, add an index that ICP could use:
      alter table orders add key i_o_clerk_date(o_clerk, o_orderDATE);
      select o_clerk, o_orderkey, o_shippriority
      from orders
      where o_clerk IN ('Clerk#000000001', 'Clerk#00000201' , 'Clerk#000003001') and
              dayofweek(o_orderDATE)=1;
+--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+
|id|select_type|table |type |possible_keys           |key           |key_len|ref |rows|Extra                |
+--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+
| 1|SIMPLE     |orders|range|i_o_clerk_date,i_o_clerk|i_o_clerk_date|16     |NULL|2978|Using index condition|
+--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+




                                                                                            48          03:14:36 PM
Counters
    alter table orders add key i_o_clerk_date(o_clerk, o_orderDATE);
    select o_clerk, o_orderkey, o_shippriority
    from orders
    where o_clerk IN ('Clerk#000000001', 'Clerk#00000201' , 'Clerk#000003001') and
            dayofweek(o_orderDATE)=1;
+--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+
|id|select_type|table |type |possible_keys           |key           |key_len|ref |rows|Extra                |
+--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+
| 1|SIMPLE     |orders|range|i_o_clerk_date,i_o_clerk|i_o_clerk_date|16     |NULL|2978|Using index condition|
+--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+
+----------------------------+-------+
| Variable_name              | Value |                   MariaDB [(none)]> show status like 'Innodb%';
+----------------------------+-------+                   +-----------------------------------+-------+
| Handler_commit             | 2     |                   | Variable_name                     | Value |
| Handler_delete             | 0     |                   +-----------------------------------+-------+
| Handler_discover           | 0     |                   | Innodb_buffer_pool_read_requests | 7687 | +2585
| Handler_icp_attempts       | 2979 |                    | Innodb_rows_read                  | 427   | +427
| Handler_icp_match          | 427   | = 1/7th
| Handler_mrr_init           | 0     |
| Handler_mrr_key_refills    | 0     |
| Handler_mrr_rowid_refills | 0      |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key           | 3     |
| Handler_read_next          | 427   | = 1/7th
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 0     |

                                                                                            49          03:14:37 PM
Index Condition Pushdown optimization
 … or lack thereof
    alter table orders add key i_o_clerk_date(o_clerk, o_orderDATE);
    select o_clerk, o_orderkey, o_shippriority
    from orders
    where o_clerk IN ('Clerk#000000001', 'Clerk#00000201' , 'Clerk#000003001') and
            dayofweek(o_orderDATE)=1;

INDEX (o_clerk)                          INDEX (o_clerk, o_orderDATE);
•   Range selectivity on 1st keypart is (or, should be) the same for both
•    Index entries are smaller            •   ICP is applicable
                                              •   But is condition dayofweek(...)=1
                                                  selective?

    Current optimizer actions:
     •   #1. Choose an index to use
     •   #2. If possible, use IndexConditionPushdown with it
          •   => Possibility of ICP doesn't affect index choice.
                                                                          50         03:14:37 PM
Index Condition Pushdown optimization
 … or lack thereof


What to do, then?
•   USE/IGNORE INDEX hints
•   Create only indexes such that ICP will be used
    •   Include approriate extra columns at the end
    •   In our example:
        •   Create INDEX (o_clerk, o_orderDATE)
        •   Don't create INDEX (o_clerk)




                                                      51   03:14:37 PM
Index Merge




              52   03:14:37 PM
Index Merge Union

•    ORed conditions on different columns cannot be resolved
     with any single index
•    Example: airline on-time performance data, 15M flights:
    select * from ontime where (Origin='SEA' or Dest='SEA');



    Origin='SEA'




    Dest='SEA'

                                                     53        03:14:37 PM
Index Merge union

      select * from ontime where (Origin='SEA' or Dest='SEA');
+--+-----------+------+-----------+-------------+-----------+-------+----+-----+-------------------------------------+
|id|select_type|table |type       |possible_keys|key        |key_len|ref |rows |Extra                                |
+--+-----------+------+-----------+-------------+-----------+-------+----+-----+-------------------------------------+
| 1|SIMPLE     |ontime|index_merge|Origin,Dest |Origin,Dest|6,6     |NULL|92850|Using union(Origin,Dest); Using where|
+--+-----------+------+-----------+-------------+-----------+-------+----+-----+-------------------------------------+


  •   Optimization is challenging:
                                               OR
          Origin='SEA'                                                 Dest='SEA'




                v.s.
                                                                  ...no way to tell.
                                                                  Current assumtion:
                                                                  pessimistic (top)
                                                                                               54          03:14:37 PM
Index merge: statistics counters

select count(*) from ontime where (Origin='SEA' or Dest='SEA'); -- 46412 rows
   +----------------------------+--------+
   | Variable_name              | Value |
   +----------------------------+--------+
   ...
   | Handler_read_first         | 0      |
   | Handler_read_key           | 2      |
   | Handler_read_next          | 46412 |
   | Handler_read_prev          | 0      |
   | Handler_read_rnd           | 46412 |
   | Handler_read_rnd_deleted   | 0      |
   | Handler_read_rnd_next      | 0      |
   ...
   | Innodb_rows_read           | 464120 | +92824


select count(*) from ontime where Origin='SEA' -- 23207 rows
   +----------------------------+--------+
   | Variable_name              | Value |
   +----------------------------+--------+
   ...
   | Handler_read_first         | 0      |
   | Handler_read_key           | 1      |
   | Handler_read_next          | 23207 |
   | Handler_read_prev          | 0      |
   | Handler_read_rnd           | 0      |
   | Handler_read_rnd_deleted   | 0      |
   | Handler_read_rnd_next      | 0      |
   ...
   | Innodb_rows_read           | 556943 | +23207        1.5X reads look like 3x!
                                                                       55       03:14:38 PM
Index merge properties
[MySQL, Percona, MariaDB 5.2x]
 index_merge plans are removed from consideration if a range
 access is possible:
MySQL [ontime]> explain select * from ontime where (Origin='SEA' or Dest='SEA') and securitydelay=0;
+--+-----------+------+----+-------------------------+-------------+-------+-----+------+-----------+
|id|select_type|table |type|possible_keys            |key          |key_len|ref |rows |Extra         |
+--+-----------+------+----+-------------------------+-------------+-------+-----+------+-----------+
| 1|SIMPLE     |ontime|ref |Origin,Dest,SecurityDelay|SecurityDelay|5      |const|791546|Using where|
+--+-----------+------+----+-------------------------+-------------+-------+-----+------+-----------+


MySQL [ontime]> explain select * from ontime where (Origin='SEA' or Dest='SEA') and depdelay < 12*60;
+--+-----------+------+----+--------------------+----+-------+----+-------+-----------+
|id|select_type|table |type|possible_keys       |key |key_len|ref |rows   |Extra      |
+--+-----------+------+----+--------------------+----+-------+----+-------+-----------+
| 1|SIMPLE     |ontime|ALL |Origin,DepDelay,Dest|NULL|NULL   |NULL|1583093|Using where|
+--+-----------+------+----+--------------------+----+-------+----+-------+-----------+

Solutions:
  •   Upgrade to MariaDB 5.3+ :-)
  •   IGNORE INDEX($index_used_for_range)
      •   there is no “FORCE INDEX MERGE” hint
                                                                                       56         03:14:38 PM
Index Merge/Union summary

•   Used for ORs spanning multiple indexes
    •   Each part of OR must have an index it could use
•   Counter increments not quite comparable with single-
    index access methods
•   Can be turned off globally with
    set optimizer_switch='index_merge=off'
•   Can be turned off locally with
    IGNORE INDEX(some_merged_index)
•   Can be blocked by potential ref/range accesses
    •   Fix1: Upgrade to MariaDB 5.3
    •   Fix2: IGNORE INDEX(other_range_indexes)
              USE INDEX (index_merge_indexes)      57     03:14:38 PM
Index Merge/Intersection
       •    Like Index Merge/union, but for ANDs
       •    Aimed at cases when there is no composite index
           select avg(arrdelay) from ontime where depdel15=1 and OriginState ='CA';
+-----------+------+-----------+--------------------+--------------------+-------+----+-----+-----------------------------------------
|select_type|table |type       |possible_keys       |key                 |key_len|ref |rows |Extra
+-----------+------+-----------+--------------------+--------------------+-------+----+-----+-----------------------------------------
|SIMPLE     |ontime|index_merge|OriginState,DepDel15|OriginState,DepDel15|3,5    |NULL|76952|Using intersect(OriginState,DepDel15);Usi
+-----------+------+-----------+--------------------+--------------------+-------+----+-----+-----------------------------------------




      •     MySQL, MariaDB 5.2: only support equality conditions
      •     MariaDB 5.3: any range condition supported
             •   Must be manually enabled:
                 set optimizer_switch='index_merge_sort_intersection=on'
             •   EXPLAIN: Using sort-intersect
                                                                                                           58            03:14:38 PM
Index Merge/intersect optimization

•   Optimization challenges are similar to UNION
select avg(arrdelay) from ontime where depdel15=1 and OriginState ='CA';




•   There is no way to force index_merge/intersect
•   To disable
    •   set optimizer_switch='index_merge_intersection=off
    •   IGNORE INDEX(one_of_used_indexes)
•   Tip: showeling through lots of data?
    set optimizer_switch='index_merge_sort_intersection=on'
    set sort_buffer_size=...
                                                               59          03:14:38 PM
JOINs


        60   03:14:38 PM
Consider a join

  select count(*)
  from
    customer, orders
  where
    c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT';
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
|id|select_type|table   |type |possible_keys|key        |key_len|ref               |rows|Extra
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
| 1|SIMPLE     |customer|range|...          |c_acctbal |9       |NULL              |6802|Using where; Using in
| 1|SIMPLE     |orders |ref |i_o_custkey |i_o_custkey|5         |customer.c_custkey|   7|Using where
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------




                                                                                        61         03:14:38 PM
Nested loops join

  select count(*)
  from
    customer, orders
  where
    c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT';
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
|id|select_type|table   |type |possible_keys|key        |key_len|ref               |rows|Extra
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
| 1|SIMPLE     |customer|range|...          |c_acctbal |9       |NULL              |6802|Using where; Using in
| 1|SIMPLE     |orders |ref |i_o_custkey |i_o_custkey|5         |customer.c_custkey|   7|Using where
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------




          for each record R1 in customer
          {


               for each matching record R2 in orders
               {

                    pass (R1, R2) into join output          •   EXPLAIN shows the loops,
               }
                                                                from outer to inner
          }
                                                                                        62         03:14:39 PM
Nested loops join

  select count(*)
  from
    customer, orders
  where
    c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT';
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
|id|select_type|table   |type |possible_keys|key        |key_len|ref               |rows|Extra
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
| 1|SIMPLE     |customer|range|...          |c_acctbal |9       |NULL              |6802|Using where; Using in
| 1|SIMPLE     |orders |ref |i_o_custkey |i_o_custkey|5         |customer.c_custkey|   7|Using where
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------




          for each record R1 in customer
          {
            if (where[customer] is satisfied)
            {
              for each matching record R2 in orders
              {                                                 •   “Using where” means
                if (where[orders] is satisfied)
                  pass (R1, R2) into join output                    checking a part of WHERE
                  }                                             •   Which part?
              }
          }
                                                                                        63         03:14:39 PM
Nested loops join

  select count(*)
  from
    customer, orders
  where
    c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT';
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
|id|select_type|table   |type |possible_keys|key        |key_len|ref               |rows|Extra
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
| 1|SIMPLE     |customer|range|...          |c_acctbal |9       |NULL              |6802|Using where; Using in
| 1|SIMPLE     |orders |ref |i_o_custkey |i_o_custkey|5         |customer.c_custkey|   7|Using where
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------




          for each record R1 in customer
          {
            if (where[customer] is satisfied)
            {
              for each matching record R2 in orders
              {
                                                                •   The part that refers to the
                if (where[orders] is satisfied)                     tables for which we know
                  pass (R1, R2) into join output
                                                                    R{n}, the “current record”.
                  }
              }
          }
                                                                                        64         03:14:39 PM
Nested loops join

  select count(*)
  from
    customer, orders
  where
    c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT';
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
|id|select_type|table   |type |possible_keys|key        |key_len|ref               |rows|Extra
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
| 1|SIMPLE     |customer|range|...          |c_acctbal |9       |NULL              |6802|Using where; Using in
| 1|SIMPLE     |orders |ref |i_o_custkey |i_o_custkey|5         |customer.c_custkey|   7|Using where
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------



   for each record R1 in customer // 6802 loops
   {
     if (where[customer] is satisfied)
     {
       for each matching record R2 in orders   // 7 loops
       {
         if (where[orders] is satisfied)
           pass (R1, R2) into join output
                                             •  “rows” shows         how many rows are
                                                        read from each table
           }
       }                                            •   For `orders`, 7 rows will be read
   }
                                                        6802 times
                                                                                        65         03:14:39 PM
Nested loops join
  select count(*)
  from
    customer, orders
  where
    c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT';
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
|id|select_type|table   |type |possible_keys|key        |key_len|ref               |rows|Extra
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
| 1|SIMPLE     |customer|range|...          |c_acctbal |9       |NULL              |6802|Using where; Using in
| 1|SIMPLE     |orders |ref |i_o_custkey |i_o_custkey|5         |customer.c_custkey|   7|Using where
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------


   for each record R1 in customer // 6802 loops
   {
     if (where[customer] is satisfied)
     {
       for each matching record R2 in orders  // 7 loops
       {
         if (where[orders] is satisfied)                              •   Wait, what about
           pass (R1, R2) into join output
                                                                          this 'if'?
           }
       }                                                              •   It may reject some of
   }
                                                                          the 6802 values of R1

                                                                                        66         03:14:39 PM
Nested loops join
      explain extended
      select * from customer, orders
      where
        c_custkey=o_custkey and c_acctbal < 1000 and o_orderpriority='1-URGENT';
--+-----------+--------+----+-------------+-----------+-------+------------------+------+--------+-----------+
id|select_type|table   |type|possible_keys|key        |key_len|ref               |rows |filtered|Extra       |
--+-----------+--------+----+-------------+-----------+-------+------------------+------+--------+-----------+
 1|SIMPLE     |customer|ALL |...          |NULL       |NULL   |NULL              |132987|   39.44|Using where|
 1|SIMPLE     |orders |ref |i_o_custkey |i_o_custkey|5        |customer.c_custkey|     7| 100.00|Using where|
--+-----------+--------+----+-------------+-----------+-------+------------------+------+--------+-----------+



  for each record R1 in customer // 6802 loops
  {
    if (where[customer] is satisfied)
    {
      for each matching record R2 in orders // 7 loops
      {
        if (where[orders] is satisfied)
          pass (R1, R2) into join output
                                                •  “filtered”         column shows the
                                                             percentage of records that are
          }                                                  expected to pass the “if”.
      }
  }                                                      •   100% is the most frequent
                                                             (optimizer puts 100% when there
                                                             are no reliable estimates for filtering)
                                                                                         67         03:14:39 PM
Ref access
  select count(*)
  from
    customer, orders
  where
    c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT';
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
|id|select_type|table   |type |possible_keys|key        |key_len|ref               |rows|Extra
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
| 1|SIMPLE     |customer|range|...          |c_acctbal |9       |NULL              |6802|Using where; Using in
| 1|SIMPLE     |orders |ref |i_o_custkey |i_o_custkey|5         |customer.c_custkey|   7|Using where
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------


     •   `orders` is accessed with ref access: index lookups on index
         i_o_custkey using value of customer.c_custkey
     •   This is similar to key=const ref access we've discussed earlier
          •   But there is no “const”, the value of customer.c_custkey is
              different for each lookup.
     •   Each lookup is expected to produce 7 matching rows.
          •   This information is from Index Statistics. We'll cover it later.
                                                                                        68         03:14:39 PM
How good a join can/should be




                                69   03:14:39 PM
Let's collect some data
     select count(*)
     from
       customer, orders
     where
       c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT';
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
|id|select_type|table   |type |possible_keys|key        |key_len|ref               |rows|Extra
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
| 1|SIMPLE     |customer|range|...          |c_acctbal |9       |NULL              |6802|Using where; Using in
| 1|SIMPLE     |orders |ref |i_o_custkey |i_o_custkey|5         |customer.c_custkey|   7|Using where
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------


     [explain] select count(*) from customer;              -- 150K

     [explain] select count(*) from customer where c_acctbal < -500                    -- 6802

     [explain] select count(*) from orders -- 1.5M

     [explain] select count(*) from orders where o_orderpriority='1-URGENT' – 300K

     One customer (c_custkey) – 7 orders


 •    And use it to analyze the join:
                                                                                        70         03:14:40 PM
Analyzing the join
select count(*) from customer, orders
where
 c_custkey=o_custkey and c_acctbal < 1000 and o_orderpriority='1-URGENT';



                                                There are three ways to
                                                compute the join:
                                                 •   Yellow → Green
                                                 •   Blue → Green
                                                 •   Yellow, Blue




                                                               71       03:14:40 PM
Join plan analysis
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
|id|select_type|table   |type |possible_keys|key        |key_len|ref               |rows|Extra
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------
| 1|SIMPLE     |customer|range|...          |c_acctbal |9       |NULL              |6802|Using where; Using in
| 1|SIMPLE     |orders |ref |i_o_custkey |i_o_custkey|5         |customer.c_custkey|   7|Using where
+--+-----------+--------+-----+-------------+-----------+-------+------------------+----+---------------------

                                                •   We're using the Yellow->Green
                                                    read
                                                •   The yellow part is read efficiently
                                                     •   Range access is used, it scans
                                                         6K rows
                                                •   The green part read efficiently
                                                     •   ref access, 6K lookups, each
                                                         producing 7 rows on average
                                                •   This is probably better than the
                                                    two other variants
                                                                                        72         03:14:40 PM
Tracking query plan execution

•   EXPLAINs are what the optimizer *expects * to
    happen
•   What actually happens?
•   MySQL has no EXPLAIN ANALYZE
•   Possible solutions
    •   Status variables
    •   “userstat”
    •   performance_schema?


                                           73   03:14:40 PM
Check status variables

MariaDB [dbt3sf1]> show status like 'Handler%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
                                          •   Accesses to all tables are summed
| Handler_commit             | 1     |        together!
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_icp_attempts       | 0     |   It is still possible to do analysis:
                                          •
| Handler_icp_match          | 0     |
| Handler_mrr_init
| Handler_mrr_key_refills
                             | 0
                             | 0
                                     |
                                     |
                                               • Run a “sub-join” with the 1st table;
| Handler_mrr_rowid_refills | 0      |           note the counters
| Handler_prepare            | 0     |
| Handler_read_first
| Handler_read_key
                             | 0
                             | 6805 |
                                     |         • Run a “sub-join” with the 1st and 2nd
| Handler_read_next          | 75951 |           tables from the join order;
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 0     |           note the counters;
| Handler_read_rnd_deleted   | 0     |           substract counters from #1
| Handler_read_rnd_next      | 0     |
| Handler_rollback
| Handler_savepoint
                             | 0
                             | 0
                                     |
                                     |      ...•
...
                                     •   This is slow and painful.


                                                                       74        03:14:40 PM
More powerful: userstatv2

•       Percona Server and MariaDB:
    MariaDB [dbt3sf1]> show table_statistics;
    +--------------+------------+-----------+--------------+-------------------------+
    | Table_schema | Table_name | Rows_read | Rows_changed | Rows_changed_x_#indexes |
    +--------------+------------+-----------+--------------+-------------------------+
    | dbt3sf1      | customer   |      6805 |            0 |                       0 |
    | dbt3sf1      | orders     |     69147 |            0 |                       0 |
    +--------------+------------+-----------+--------------+-------------------------+
    2 rows in set (0.00 sec)

    MariaDB [dbt3sf1]> show index_statistics;
    +--------------+------------+-------------+-----------+
    | Table_schema | Table_name | Index_name | Rows_read |
    +--------------+------------+-------------+-----------+
    | dbt3sf1      | customer   | c_acctbal   |      6805 |
    | dbt3sf1      | orders     | i_o_custkey |     69147 |
    +--------------+------------+-------------+-----------+
    2 rows in set (0.00 sec)


    •    Much better but self-joins are still painful
                                                                           75        03:14:41 PM
Recap

•   Ok we now know
    •   Single table access methods
        •   ref(const)
        •   Range
        •   index_merge
    •   Nested loop join algorithm
    •   EXPLAIN
    •   How to check if execution follows EXPLAIN


                                            76      03:14:41 PM
Typical reasons of poor join performance


•   Poor access strategy for the 1st table
    •   Highly-selective non-indexed
        conditions? Create an index!
•   Joins on non-equality conditions
    •   e.g. “same day” joins on DATETIME
        columns
    •   Tip: Convert to equality join at all costs
•   No index to do ref access for join
    •   Cross-product joins inherently slow
•   Correlated data (...)
•   Wrong index statistics (...)
                                                     77   03:14:41 PM
Non-uniform distributions

                                                  o_orderdate
•   Some data is highly non-
    uniform
                                    Christmas
•   Performance depends on
    hitting the popular values
                                   Valentine's day

•   No one-for-all solution
                                 March, nn
                                 Jne , mm




                                             78         03:14:41 PM
Index and Table Statistics



                        79   03:14:41 PM
Database statistics and plan
stability (InnoDB/XtraDB)

•   Sample [8] random index leaf pages
•   Table statistics (stored)
    •   rows - estimated number of rows in a table
    •   clust_size - cluster index (table/primary key) size in number of pages
    •   other_size - other index (non primary key) size in number of pages

•   Index statistics (stored)
    •   fields - #fields in the index
    •   rows_per_key - rows per 1 key value, per prefix fields
        ([1 column value], [2 columns value], [3 columns value], …)
    •   index_total_pages – number of index pages
    •   index_leaf_pages – number of leaf pages
                                                                   80        03:14:41 PM
Table statistics updates

•   Statistics updated when:
     •   ANALYZE TABLE tbl_name [, tbl_name] …
     •   SHOW TABLE STATUS, SHOW INDEX
     •   Access to INFORMATION_SCHEMA.[TABLES| STATISTICS]
     •   A table is opened for the first time
         (that is, after server restart)
     •   A table has changed a lot
         (1/16th of the table updated/deleted/inserted)
     •   When InnoDB Monitor is turned ON
     •   Others (?)

                                                      81   03:14:41 PM
Displaying statistics

•   MySQL 5.5, MariaDB 5.3, and older (GA versions)
    •   Issue SQL statements to count rows/keys
    •   Indirectly, look at EXPLAIN for simple queries
•   MariaDB 5.5, Percona Server 5.5 (using XtraDB) (GA versions)
    •   information_schema.[innodb_index_stats, innodb_table_stats]
    •   Read-only, always visible
•   MySQL 5.6 (development)
    •   mysql.[innodb_index_stats, innodb_table_stats]
    •   User updatetable
    •   Only available if innodb_analyze_is_persistent=ON
•   MariaDB $NEXT (development)
    •   MyISAM persistent updateable tables
    •   + current MySQL and Percona mechanisms
                                                                 82   03:14:42 PM
Plan [in]stability

•   Statistics may vary a lot (orders)
MariaDB [dbt3]> select * from information_schema.innodb_index_stats;
+------------+-----------------+--------------+    +---------------+
| table_name | index_name      | rows_per_key |    | rows_per_key |    error (actual)
+------------+-----------------+--------------+    +---------------+
| partsupp   | PRIMARY         | 3, 1         |    | 4, 1          |   25%
| partsupp   | i_ps_partkey    | 3, 0         | => | 4, 1          |   25%         (4)
| partsupp   | i_ps_suppkey    | 64, 0        |    | 91, 1         |   30%        (80)
| orders     | i_o_orderdate   | 9597, 1      |    | 1660956, 0    |   99%      (6234)
| orders     | i_o_custkey     | 15, 1        |    | 15, 0         |    0%        (15)
| lineitem   | i_l_receiptdate | 7425, 1, 1   |    | 6665850, 1, 1 |   99.9%   (23477)
+------------+-----------------+--------------+    +---------------+

MariaDB [dbt3]> select * from information_schema.innodb_table_stats;
+-----------------+----------+       +----------+
| table_name      | rows     |       | rows     |
+-----------------+----------+       +----------+
| partsupp        | 6524766 |        | 9101065 | 28%    (8000000)
| orders          | 15039855 | ==>   | 14948612 | 0.6% (15000000)
| lineitem        | 60062904 |       | 59992655 | 0.1% (59986052)
+-----------------+----------+       +----------+

                                                                       83       03:14:42 PM
Controlling statistics (GA versions)

•   MySQL 5.5, MariaDB 5.3, and older
     •   Manual tuning: optimizer hints, system variables
•   MySQL 5.5, MariaDB 5.5 with InnoDB plugin
     •   innodb_stats_on_metadata = OFF – update only on restart, ANALYZE
     •   innodb_stats_sample_pages = 8 is default - increase precision
     •   No way to “freeze” db statistics in all cases
•   MariaDB 5.5, Percona Server 5.5 (using XtraDB)
     •   Can “freeze” the current InnoDB statistics
     •   innodb_use_sys_stats_table=ON – use I_S.INNODB_SYS_STATS
     •   innodb_stats_auto_update=OFF – recalculate except for “first open”
         and “ANALYZE TABLE”
     •   No manual control over statistics, only refresh by random sampling
                                                              84         03:14:42 PM
Plan [in]stability

•   Same query, different statistics
=>
•   Different access methods and/or
•   Different JOIN orders
=> different query plans
•   Query performance may change a lot when
    statistics changes
•   BEWARE WHEN BENCHMARKING THE
    OPTIMIZER
                                        85    03:14:42 PM
Controlling statistics (MySQL dev)

MySQL 5.6 (development version, public code)
•   Persistent and user-updatetable InnoDB statistics
    •   innodb_analyze_is_persistent = ON,
    •   updated only on ANALYZE TABLE
•   Control the precision of sampling [default 8]
    •   innodb_stats_persistent_sample_pages,
    •   innodb_stats_persistent_sample_pages
•   No new statistics compared to older versions




                                                    86   03:14:42 PM
Controlling statistics (MariaDB dev)

MariaDB $NEXT (development version, public code)
•   Current MySQL and Percona InnoDB statistics
    +
•   Engine-independent, persistent, user-updateable statistics
•   Precise
•   Additional statistics per column (even when there is no index):
    •   min_value, max_value: minimum/maximum value per column
    •   nulls_ratio: fraction of null values in a column
    •   avg_length: average size of values in a column
    •   avg_frequency: average number of rows with the same value
=> better query plans
Code: bzr branch lp:~maria-captains/maria/maria-5.5-mwl248
                                                            87        03:14:42 PM
Resources on InnoDB statistics

From the InnoDB/XtraDB developers:
https://mysqlperformanceblog.com/doc/percona-server/5.5/diagnostics/innodb_stats.html

http://dev.mysql.com/doc/refman/5.6/en/innodb-other-changes-statistics-estimation.html

http://dev.mysql.com/doc/refman/5.6/en/innodb-parameters.html

http://dev.mysql.com/doc/refman/5.6/en/innodb-performance.html#innodb-persistent-stats

http://blogs.innodb.com/wp/2011/04/innodb-persistent-statistics-at-last/

http://dev.mysql.com/doc/refman/5.6/en/analyze-table.html




                                                                             88          03:14:42 PM
Optimizer differences between
      MySQL branches



                          89    03:14:42 PM
Optimizer in different MySQL branches

•   Base version: MySQL 5.5
    •   Optimizer is not different from MySQL 5.1
•   Percona Server X.Y
    •   Closely follows MySQL X.Y
    •   Some improvements in diagnostics (eg. userstats)
•   MariaDB 5.3
    •   Lots of new features
•   MariaDB 5.5
    •   For the most part, is a merge of Maria-5.3 with MySQL-5.5
    •   But has a couple of small improvements
•   MySQL 5.6
    •   Lots of new features, intersection with MariaDB 5.3

                                                              90    03:14:43 PM
Optimizer feature comparison (1)
Feature                                                MariaDB      MySQL 5.5 MySQL
                                                       5.3/5.5                (5.6 dev)

Disk access optimizations
Index Condition Pushdown (ICP)                            YES           -         YES
Disk-sweep Multi-range read (DS-MRR)                      YES           -         YES
DS-MRR with Key-ordered retrieval                         YES           -          -
Index_merge / Sort_intersection                           YES           -          -
Cost-based choice of range vs. index_merge                YES           -          -
ORDER BY .. LIMIT <small_limit>                             -           -         YES
Use extended (hidden) primary keys for innodb/xtradb    YES (5.5)       -          -
Join optimizations
Batched key access (BKA)                                  YES           -         YES
Block hash join                                           YES           -          -
User-set memory limits on all join buffers                YES           -          -
Apply early outer table ON conditions                     YES           -          -
Null-rejecting conditions tested early for NULLs          YES           -          -
                                                                            91      03:14:43 PM
Optimizer feature comparison (2)

Feature                                                          MariaDB   MySQL        MySQL
                                                                 5.5       5.5          (5.6 dev)

Subquery optimizations
In-to-exists                                                       YES      YES           YES
Semi-join                                                          YES        -           -YES
Materialization                                                    YES        -           YES
NULL-aware Materialization                                         YES        -             -
Cost choice of materialization vs in-to-exists                     YES        -             -
Subquery cache                                                     YES        -             -
Fast explain with subqueries                                       YES                      -
Optimizations for derived tables / views
Delayed materialization of derived tables / materialized views     YES        -           YES
Instant EXPLAIN for derived tables                                 YES        -           YES
Derived Table with Keys optimization                               YES        -           YES
Fields of merge-able views and derived tables used in              YES        -             -
equality optimizations
                                                                                   92           03:14:43 PM
Optimizer feature comparison (3)


Feature                                               MariaDB      MySQL 5.5 MySQL
                                                      5.3/5.5                (5.6 dev)

Execution control
LIMIT ROWS EXAMINED rows_limit                         YES (5.5)       -           -
Optimizer control (optimizer switch)
Systematic control of all optimizer strategies           YES           -        partial
EXPLAIN improvements
Explain for DELETE, INSERT, REPLACE, and UPDATE            -           -         YES
EXPLAIN in JSON format                                     -           -         YES
More detailed and consistent EXPLAIN for subqueries      YES           -           -




                                                                           93          03:14:43 PM
Optimizer switch (2)

MariaDB 5.5                         MySQL 5.6
Index merge:                        Index merge:

index_merge=on,                     index_merge=on,
index_merge_union=on,               index_merge_union=on,
index_merge_sort_union=on,          index_merge_sort_union=on,
index_merge_intersection=on,        index_merge_intersection=on
index_merge_sort_intersection=off

Condition pushdown:                 Condition pushdown:

engine_condition_pushdown=off,      engine_condition_pushdown=on,
index_condition_pushdown=on,        index_condition_pushdown=on,

mrr=off,                            mrr=on,
mrr_cost_based=off,                 mrr_cost_based=on
mrr_sort_keys=off


                                                           94       03:14:43 PM
Optimizer switch (2)

MariaDB 5.5                     MySQL 5.6
Subqueries:                     Subqueries:
materialization=on,             materialization=on,
semijoin=on,                    semijoin=on,
loosescan=on,                   loosescan=on,
firstmatch=on,                  firstmatch=on
in_to_exists=on,
partial_match_rowid_merge=on,
partial_match_table_scan=on,
subquery_cache=on,

Derived tables:
derived_merge=on,
derived_with_keys=on




                                                      95   03:14:43 PM
Optimizer switch (3)

MariaDB 5.5                     MySQL 5.6
Joins                           Joins:
join_cache_bka=on,              batched_key_access=off
join_cache_incremental=on,      block_nested_loop=on,
join_cache_hashed=on,
outer_join_with_cache=on,
semijoin_with_cache=on,
optimize_join_buffer_size=off
+ other system variables

Other features
table_elimination=on,
extended_keys=off




                                                         96   03:14:43 PM
Thank You!

  Q&A




             97   03:14:44 PM

Weitere ähnliche Inhalte

Was ist angesagt?

New features in Performance Schema 5.7 in action
New features in Performance Schema 5.7 in actionNew features in Performance Schema 5.7 in action
New features in Performance Schema 5.7 in actionSveta Smirnova
 
MySQL Query tuning 101
MySQL Query tuning 101MySQL Query tuning 101
MySQL Query tuning 101Sveta Smirnova
 
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013Jaime Crespo
 
Full Table Scan: friend or foe
Full Table Scan: friend or foeFull Table Scan: friend or foe
Full Table Scan: friend or foeMauro Pagano
 
Why Use EXPLAIN FORMAT=JSON?
 Why Use EXPLAIN FORMAT=JSON?  Why Use EXPLAIN FORMAT=JSON?
Why Use EXPLAIN FORMAT=JSON? Sveta Smirnova
 
Chasing the optimizer
Chasing the optimizerChasing the optimizer
Chasing the optimizerMauro Pagano
 
Moving to the NoSQL side: MySQL JSON functions
 Moving to the NoSQL side: MySQL JSON functions Moving to the NoSQL side: MySQL JSON functions
Moving to the NoSQL side: MySQL JSON functionsSveta Smirnova
 
Adapting to Adaptive Plans on 12c
Adapting to Adaptive Plans on 12cAdapting to Adaptive Plans on 12c
Adapting to Adaptive Plans on 12cMauro Pagano
 
Optimizer Trace Walkthrough
Optimizer Trace WalkthroughOptimizer Trace Walkthrough
Optimizer Trace WalkthroughSergey Petrunya
 
Query Optimizer in MariaDB 10.4
Query Optimizer in MariaDB 10.4Query Optimizer in MariaDB 10.4
Query Optimizer in MariaDB 10.4Sergey Petrunya
 
Histograms in 12c era
Histograms in 12c eraHistograms in 12c era
Histograms in 12c eraMauro Pagano
 
Oracle statistics by example
Oracle statistics by exampleOracle statistics by example
Oracle statistics by exampleMauro Pagano
 
Same plan different performance
Same plan different performanceSame plan different performance
Same plan different performanceMauro Pagano
 
Demystifying cost based optimization
Demystifying cost based optimizationDemystifying cost based optimization
Demystifying cost based optimizationRiyaj Shamsudeen
 
M|18 Querying Data at a Previous Point in Time
M|18 Querying Data at a Previous Point in TimeM|18 Querying Data at a Previous Point in Time
M|18 Querying Data at a Previous Point in TimeMariaDB plc
 
Optimizer features in recent releases of other databases
Optimizer features in recent releases of other databasesOptimizer features in recent releases of other databases
Optimizer features in recent releases of other databasesSergey Petrunya
 
0888 learning-mysql
0888 learning-mysql0888 learning-mysql
0888 learning-mysqlsabir18
 
Introduction into MySQL Query Tuning for Dev[Op]s
Introduction into MySQL Query Tuning for Dev[Op]sIntroduction into MySQL Query Tuning for Dev[Op]s
Introduction into MySQL Query Tuning for Dev[Op]sSveta Smirnova
 
Using histograms to get better performance
Using histograms to get better performanceUsing histograms to get better performance
Using histograms to get better performanceSergey Petrunya
 

Was ist angesagt? (20)

New features in Performance Schema 5.7 in action
New features in Performance Schema 5.7 in actionNew features in Performance Schema 5.7 in action
New features in Performance Schema 5.7 in action
 
MySQL Query tuning 101
MySQL Query tuning 101MySQL Query tuning 101
MySQL Query tuning 101
 
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
Query Optimization with MySQL 5.6: Old and New Tricks - Percona Live London 2013
 
Full Table Scan: friend or foe
Full Table Scan: friend or foeFull Table Scan: friend or foe
Full Table Scan: friend or foe
 
Why Use EXPLAIN FORMAT=JSON?
 Why Use EXPLAIN FORMAT=JSON?  Why Use EXPLAIN FORMAT=JSON?
Why Use EXPLAIN FORMAT=JSON?
 
Chasing the optimizer
Chasing the optimizerChasing the optimizer
Chasing the optimizer
 
Moving to the NoSQL side: MySQL JSON functions
 Moving to the NoSQL side: MySQL JSON functions Moving to the NoSQL side: MySQL JSON functions
Moving to the NoSQL side: MySQL JSON functions
 
Adapting to Adaptive Plans on 12c
Adapting to Adaptive Plans on 12cAdapting to Adaptive Plans on 12c
Adapting to Adaptive Plans on 12c
 
Optimizer Trace Walkthrough
Optimizer Trace WalkthroughOptimizer Trace Walkthrough
Optimizer Trace Walkthrough
 
Query Optimizer in MariaDB 10.4
Query Optimizer in MariaDB 10.4Query Optimizer in MariaDB 10.4
Query Optimizer in MariaDB 10.4
 
Histograms in 12c era
Histograms in 12c eraHistograms in 12c era
Histograms in 12c era
 
MySQL SQL Tutorial
MySQL SQL TutorialMySQL SQL Tutorial
MySQL SQL Tutorial
 
Oracle statistics by example
Oracle statistics by exampleOracle statistics by example
Oracle statistics by example
 
Same plan different performance
Same plan different performanceSame plan different performance
Same plan different performance
 
Demystifying cost based optimization
Demystifying cost based optimizationDemystifying cost based optimization
Demystifying cost based optimization
 
M|18 Querying Data at a Previous Point in Time
M|18 Querying Data at a Previous Point in TimeM|18 Querying Data at a Previous Point in Time
M|18 Querying Data at a Previous Point in Time
 
Optimizer features in recent releases of other databases
Optimizer features in recent releases of other databasesOptimizer features in recent releases of other databases
Optimizer features in recent releases of other databases
 
0888 learning-mysql
0888 learning-mysql0888 learning-mysql
0888 learning-mysql
 
Introduction into MySQL Query Tuning for Dev[Op]s
Introduction into MySQL Query Tuning for Dev[Op]sIntroduction into MySQL Query Tuning for Dev[Op]s
Introduction into MySQL Query Tuning for Dev[Op]s
 
Using histograms to get better performance
Using histograms to get better performanceUsing histograms to get better performance
Using histograms to get better performance
 

Andere mochten auch

MySQL Performance Optimization #JDNL13
MySQL Performance Optimization #JDNL13MySQL Performance Optimization #JDNL13
MySQL Performance Optimization #JDNL13Eli Aschkenasy
 
Mysql query optimization
Mysql query optimizationMysql query optimization
Mysql query optimizationBaohua Cai
 
Understanding Query Execution
Understanding Query ExecutionUnderstanding Query Execution
Understanding Query Executionwebhostingguy
 
Query Execution Time and Query Optimization.
Query Execution Time and Query Optimization.Query Execution Time and Query Optimization.
Query Execution Time and Query Optimization.Radhe Krishna Rajan
 
56 Query Optimization
56 Query Optimization56 Query Optimization
56 Query OptimizationMYXPLAIN
 
How mysql choose the execution plan
How mysql choose the execution planHow mysql choose the execution plan
How mysql choose the execution plan辛鹤 李
 
Scaling MySQL Strategies for Developers
Scaling MySQL Strategies for DevelopersScaling MySQL Strategies for Developers
Scaling MySQL Strategies for DevelopersJonathan Levin
 
Upgrade to MySQL 5.7 and latest news planned for MySQL 8
Upgrade to MySQL 5.7 and latest news planned for MySQL 8Upgrade to MySQL 5.7 and latest news planned for MySQL 8
Upgrade to MySQL 5.7 and latest news planned for MySQL 8Ted Wennmark
 
The New MariaDB Offering: MariaDB 10, MaxScale and More
The New MariaDB Offering: MariaDB 10, MaxScale and MoreThe New MariaDB Offering: MariaDB 10, MaxScale and More
The New MariaDB Offering: MariaDB 10, MaxScale and MoreMariaDB Corporation
 
MariaDB Optimizer
MariaDB OptimizerMariaDB Optimizer
MariaDB OptimizerJongJin Lee
 
Covering indexes
Covering indexesCovering indexes
Covering indexesMYXPLAIN
 
Java MySQL Connector & Connection Pool Features & Optimization
Java MySQL Connector & Connection Pool Features & OptimizationJava MySQL Connector & Connection Pool Features & Optimization
Java MySQL Connector & Connection Pool Features & OptimizationKenny Gryp
 
Query Optimization with MySQL 5.7 and MariaDB 10: Even newer tricks
Query Optimization with MySQL 5.7 and MariaDB 10: Even newer tricksQuery Optimization with MySQL 5.7 and MariaDB 10: Even newer tricks
Query Optimization with MySQL 5.7 and MariaDB 10: Even newer tricksJaime Crespo
 
MySQL Slow Query log Monitoring using Beats & ELK
MySQL Slow Query log Monitoring using Beats & ELKMySQL Slow Query log Monitoring using Beats & ELK
MySQL Slow Query log Monitoring using Beats & ELKYoungHeon (Roy) Kim
 
Advanced MySQL Query and Schema Tuning
Advanced MySQL Query and Schema TuningAdvanced MySQL Query and Schema Tuning
Advanced MySQL Query and Schema TuningMYXPLAIN
 

Andere mochten auch (20)

MySQL Query Browser
MySQL Query BrowserMySQL Query Browser
MySQL Query Browser
 
Adaptive Query Optimization
Adaptive Query OptimizationAdaptive Query Optimization
Adaptive Query Optimization
 
MySQL 5.1 and beyond
MySQL 5.1 and beyondMySQL 5.1 and beyond
MySQL 5.1 and beyond
 
MySQL Performance Optimization #JDNL13
MySQL Performance Optimization #JDNL13MySQL Performance Optimization #JDNL13
MySQL Performance Optimization #JDNL13
 
Mysql query optimization
Mysql query optimizationMysql query optimization
Mysql query optimization
 
Understanding Query Execution
Understanding Query ExecutionUnderstanding Query Execution
Understanding Query Execution
 
Query Execution Time and Query Optimization.
Query Execution Time and Query Optimization.Query Execution Time and Query Optimization.
Query Execution Time and Query Optimization.
 
56 Query Optimization
56 Query Optimization56 Query Optimization
56 Query Optimization
 
How mysql choose the execution plan
How mysql choose the execution planHow mysql choose the execution plan
How mysql choose the execution plan
 
Scaling MySQL Strategies for Developers
Scaling MySQL Strategies for DevelopersScaling MySQL Strategies for Developers
Scaling MySQL Strategies for Developers
 
Mysql Optimization
Mysql OptimizationMysql Optimization
Mysql Optimization
 
Upgrade to MySQL 5.7 and latest news planned for MySQL 8
Upgrade to MySQL 5.7 and latest news planned for MySQL 8Upgrade to MySQL 5.7 and latest news planned for MySQL 8
Upgrade to MySQL 5.7 and latest news planned for MySQL 8
 
The New MariaDB Offering: MariaDB 10, MaxScale and More
The New MariaDB Offering: MariaDB 10, MaxScale and MoreThe New MariaDB Offering: MariaDB 10, MaxScale and More
The New MariaDB Offering: MariaDB 10, MaxScale and More
 
MariaDB Optimizer
MariaDB OptimizerMariaDB Optimizer
MariaDB Optimizer
 
Covering indexes
Covering indexesCovering indexes
Covering indexes
 
Java MySQL Connector & Connection Pool Features & Optimization
Java MySQL Connector & Connection Pool Features & OptimizationJava MySQL Connector & Connection Pool Features & Optimization
Java MySQL Connector & Connection Pool Features & Optimization
 
MySQL Cheat Sheet
MySQL Cheat SheetMySQL Cheat Sheet
MySQL Cheat Sheet
 
Query Optimization with MySQL 5.7 and MariaDB 10: Even newer tricks
Query Optimization with MySQL 5.7 and MariaDB 10: Even newer tricksQuery Optimization with MySQL 5.7 and MariaDB 10: Even newer tricks
Query Optimization with MySQL 5.7 and MariaDB 10: Even newer tricks
 
MySQL Slow Query log Monitoring using Beats & ELK
MySQL Slow Query log Monitoring using Beats & ELKMySQL Slow Query log Monitoring using Beats & ELK
MySQL Slow Query log Monitoring using Beats & ELK
 
Advanced MySQL Query and Schema Tuning
Advanced MySQL Query and Schema TuningAdvanced MySQL Query and Schema Tuning
Advanced MySQL Query and Schema Tuning
 

Ähnlich wie Percona live-2012-optimizer-tuning

Query Optimization with MySQL 5.6: Old and New Tricks
Query Optimization with MySQL 5.6: Old and New TricksQuery Optimization with MySQL 5.6: Old and New Tricks
Query Optimization with MySQL 5.6: Old and New TricksMYXPLAIN
 
Performance Schema for MySQL Troubleshooting
Performance Schema for MySQL TroubleshootingPerformance Schema for MySQL Troubleshooting
Performance Schema for MySQL TroubleshootingSveta Smirnova
 
How to Avoid Pitfalls in Schema Upgrade with Galera
How to Avoid Pitfalls in Schema Upgrade with GaleraHow to Avoid Pitfalls in Schema Upgrade with Galera
How to Avoid Pitfalls in Schema Upgrade with GaleraSveta Smirnova
 
Advanced Query Optimizer Tuning and Analysis
Advanced Query Optimizer Tuning and AnalysisAdvanced Query Optimizer Tuning and Analysis
Advanced Query Optimizer Tuning and AnalysisMYXPLAIN
 
MySQL Performance Schema in 20 Minutes
 MySQL Performance Schema in 20 Minutes MySQL Performance Schema in 20 Minutes
MySQL Performance Schema in 20 MinutesSveta Smirnova
 
Beyond php - it's not (just) about the code
Beyond php - it's not (just) about the codeBeyond php - it's not (just) about the code
Beyond php - it's not (just) about the codeWim Godden
 
Empower my sql server administration with 5.7 instruments
Empower my sql server administration with 5.7 instrumentsEmpower my sql server administration with 5.7 instruments
Empower my sql server administration with 5.7 instrumentsMarco Tusa
 
Oracle Query Tuning Tips - Get it Right the First Time
Oracle Query Tuning Tips - Get it Right the First TimeOracle Query Tuning Tips - Get it Right the First Time
Oracle Query Tuning Tips - Get it Right the First TimeDean Richards
 
Applied Partitioning And Scaling Your Database System Presentation
Applied Partitioning And Scaling Your Database System PresentationApplied Partitioning And Scaling Your Database System Presentation
Applied Partitioning And Scaling Your Database System PresentationRichard Crowley
 
Sql and PL/SQL Best Practices I
Sql and PL/SQL Best Practices ISql and PL/SQL Best Practices I
Sql and PL/SQL Best Practices ICarlos Oliveira
 
How to Avoid Pitfalls in Schema Upgrade with Percona XtraDB Cluster
How to Avoid Pitfalls in Schema Upgrade with Percona XtraDB ClusterHow to Avoid Pitfalls in Schema Upgrade with Percona XtraDB Cluster
How to Avoid Pitfalls in Schema Upgrade with Percona XtraDB ClusterSveta Smirnova
 
MySQL 5.7. Tutorial - Dutch PHP Conference 2015
MySQL 5.7. Tutorial - Dutch PHP Conference 2015MySQL 5.7. Tutorial - Dutch PHP Conference 2015
MySQL 5.7. Tutorial - Dutch PHP Conference 2015Dave Stokes
 
MySQL 5.7 Tutorial Dutch PHP Conference 2015
MySQL 5.7 Tutorial Dutch PHP Conference 2015MySQL 5.7 Tutorial Dutch PHP Conference 2015
MySQL 5.7 Tutorial Dutch PHP Conference 2015Dave Stokes
 
Need for Speed: MySQL Indexing
Need for Speed: MySQL IndexingNeed for Speed: MySQL Indexing
Need for Speed: MySQL IndexingMYXPLAIN
 
Embarcadero In Search of Plan Stability Part 1 Webinar Slides
Embarcadero In Search of Plan Stability Part 1 Webinar SlidesEmbarcadero In Search of Plan Stability Part 1 Webinar Slides
Embarcadero In Search of Plan Stability Part 1 Webinar SlidesEmbarcadero Technologies
 

Ähnlich wie Percona live-2012-optimizer-tuning (20)

Query Optimization with MySQL 5.6: Old and New Tricks
Query Optimization with MySQL 5.6: Old and New TricksQuery Optimization with MySQL 5.6: Old and New Tricks
Query Optimization with MySQL 5.6: Old and New Tricks
 
Performance Schema for MySQL Troubleshooting
Performance Schema for MySQL TroubleshootingPerformance Schema for MySQL Troubleshooting
Performance Schema for MySQL Troubleshooting
 
How to Avoid Pitfalls in Schema Upgrade with Galera
How to Avoid Pitfalls in Schema Upgrade with GaleraHow to Avoid Pitfalls in Schema Upgrade with Galera
How to Avoid Pitfalls in Schema Upgrade with Galera
 
DPC Tutorial
DPC TutorialDPC Tutorial
DPC Tutorial
 
Advanced Query Optimizer Tuning and Analysis
Advanced Query Optimizer Tuning and AnalysisAdvanced Query Optimizer Tuning and Analysis
Advanced Query Optimizer Tuning and Analysis
 
MySQL Performance Schema in 20 Minutes
 MySQL Performance Schema in 20 Minutes MySQL Performance Schema in 20 Minutes
MySQL Performance Schema in 20 Minutes
 
Beyond php - it's not (just) about the code
Beyond php - it's not (just) about the codeBeyond php - it's not (just) about the code
Beyond php - it's not (just) about the code
 
Tek tutorial
Tek tutorialTek tutorial
Tek tutorial
 
Perf Tuning Short
Perf Tuning ShortPerf Tuning Short
Perf Tuning Short
 
Empower my sql server administration with 5.7 instruments
Empower my sql server administration with 5.7 instrumentsEmpower my sql server administration with 5.7 instruments
Empower my sql server administration with 5.7 instruments
 
Oracle Query Tuning Tips - Get it Right the First Time
Oracle Query Tuning Tips - Get it Right the First TimeOracle Query Tuning Tips - Get it Right the First Time
Oracle Query Tuning Tips - Get it Right the First Time
 
Applied Partitioning And Scaling Your Database System Presentation
Applied Partitioning And Scaling Your Database System PresentationApplied Partitioning And Scaling Your Database System Presentation
Applied Partitioning And Scaling Your Database System Presentation
 
Mysql tracing
Mysql tracingMysql tracing
Mysql tracing
 
Mysql tracing
Mysql tracingMysql tracing
Mysql tracing
 
Sql and PL/SQL Best Practices I
Sql and PL/SQL Best Practices ISql and PL/SQL Best Practices I
Sql and PL/SQL Best Practices I
 
How to Avoid Pitfalls in Schema Upgrade with Percona XtraDB Cluster
How to Avoid Pitfalls in Schema Upgrade with Percona XtraDB ClusterHow to Avoid Pitfalls in Schema Upgrade with Percona XtraDB Cluster
How to Avoid Pitfalls in Schema Upgrade with Percona XtraDB Cluster
 
MySQL 5.7. Tutorial - Dutch PHP Conference 2015
MySQL 5.7. Tutorial - Dutch PHP Conference 2015MySQL 5.7. Tutorial - Dutch PHP Conference 2015
MySQL 5.7. Tutorial - Dutch PHP Conference 2015
 
MySQL 5.7 Tutorial Dutch PHP Conference 2015
MySQL 5.7 Tutorial Dutch PHP Conference 2015MySQL 5.7 Tutorial Dutch PHP Conference 2015
MySQL 5.7 Tutorial Dutch PHP Conference 2015
 
Need for Speed: MySQL Indexing
Need for Speed: MySQL IndexingNeed for Speed: MySQL Indexing
Need for Speed: MySQL Indexing
 
Embarcadero In Search of Plan Stability Part 1 Webinar Slides
Embarcadero In Search of Plan Stability Part 1 Webinar SlidesEmbarcadero In Search of Plan Stability Part 1 Webinar Slides
Embarcadero In Search of Plan Stability Part 1 Webinar Slides
 

Mehr von Sergey Petrunya

New optimizer features in MariaDB releases before 10.12
New optimizer features in MariaDB releases before 10.12New optimizer features in MariaDB releases before 10.12
New optimizer features in MariaDB releases before 10.12Sergey Petrunya
 
MariaDB's join optimizer: how it works and current fixes
MariaDB's join optimizer: how it works and current fixesMariaDB's join optimizer: how it works and current fixes
MariaDB's join optimizer: how it works and current fixesSergey Petrunya
 
Improved histograms in MariaDB 10.8
Improved histograms in MariaDB 10.8Improved histograms in MariaDB 10.8
Improved histograms in MariaDB 10.8Sergey Petrunya
 
Improving MariaDB’s Query Optimizer with better selectivity estimates
Improving MariaDB’s Query Optimizer with better selectivity estimatesImproving MariaDB’s Query Optimizer with better selectivity estimates
Improving MariaDB’s Query Optimizer with better selectivity estimatesSergey Petrunya
 
JSON Support in MariaDB: News, non-news and the bigger picture
JSON Support in MariaDB: News, non-news and the bigger pictureJSON Support in MariaDB: News, non-news and the bigger picture
JSON Support in MariaDB: News, non-news and the bigger pictureSergey Petrunya
 
ANALYZE for Statements - MariaDB's hidden gem
ANALYZE for Statements - MariaDB's hidden gemANALYZE for Statements - MariaDB's hidden gem
ANALYZE for Statements - MariaDB's hidden gemSergey Petrunya
 
MariaDB 10.4 - что нового
MariaDB 10.4 - что новогоMariaDB 10.4 - что нового
MariaDB 10.4 - что новогоSergey Petrunya
 
MariaDB Optimizer - further down the rabbit hole
MariaDB Optimizer - further down the rabbit holeMariaDB Optimizer - further down the rabbit hole
MariaDB Optimizer - further down the rabbit holeSergey Petrunya
 
Lessons for the optimizer from running the TPC-DS benchmark
Lessons for the optimizer from running the TPC-DS benchmarkLessons for the optimizer from running the TPC-DS benchmark
Lessons for the optimizer from running the TPC-DS benchmarkSergey Petrunya
 
MariaDB 10.3 Optimizer - where does it stand
MariaDB 10.3 Optimizer - where does it standMariaDB 10.3 Optimizer - where does it stand
MariaDB 10.3 Optimizer - where does it standSergey Petrunya
 
MyRocks in MariaDB | M18
MyRocks in MariaDB | M18MyRocks in MariaDB | M18
MyRocks in MariaDB | M18Sergey Petrunya
 
New Query Optimizer features in MariaDB 10.3
New Query Optimizer features in MariaDB 10.3New Query Optimizer features in MariaDB 10.3
New Query Optimizer features in MariaDB 10.3Sergey Petrunya
 
Histograms in MariaDB, MySQL and PostgreSQL
Histograms in MariaDB, MySQL and PostgreSQLHistograms in MariaDB, MySQL and PostgreSQL
Histograms in MariaDB, MySQL and PostgreSQLSergey Petrunya
 
Common Table Expressions in MariaDB 10.2
Common Table Expressions in MariaDB 10.2Common Table Expressions in MariaDB 10.2
Common Table Expressions in MariaDB 10.2Sergey Petrunya
 
MyRocks in MariaDB: why and how
MyRocks in MariaDB: why and howMyRocks in MariaDB: why and how
MyRocks in MariaDB: why and howSergey Petrunya
 
Эволюция репликации в MySQL и MariaDB
Эволюция репликации в MySQL и MariaDBЭволюция репликации в MySQL и MariaDB
Эволюция репликации в MySQL и MariaDBSergey Petrunya
 
Common Table Expressions in MariaDB 10.2 (Percona Live Amsterdam 2016)
Common Table Expressions in MariaDB 10.2 (Percona Live Amsterdam 2016)Common Table Expressions in MariaDB 10.2 (Percona Live Amsterdam 2016)
Common Table Expressions in MariaDB 10.2 (Percona Live Amsterdam 2016)Sergey Petrunya
 
MariaDB 10.1 - что нового.
MariaDB 10.1 - что нового.MariaDB 10.1 - что нового.
MariaDB 10.1 - что нового.Sergey Petrunya
 

Mehr von Sergey Petrunya (20)

New optimizer features in MariaDB releases before 10.12
New optimizer features in MariaDB releases before 10.12New optimizer features in MariaDB releases before 10.12
New optimizer features in MariaDB releases before 10.12
 
MariaDB's join optimizer: how it works and current fixes
MariaDB's join optimizer: how it works and current fixesMariaDB's join optimizer: how it works and current fixes
MariaDB's join optimizer: how it works and current fixes
 
Improved histograms in MariaDB 10.8
Improved histograms in MariaDB 10.8Improved histograms in MariaDB 10.8
Improved histograms in MariaDB 10.8
 
Improving MariaDB’s Query Optimizer with better selectivity estimates
Improving MariaDB’s Query Optimizer with better selectivity estimatesImproving MariaDB’s Query Optimizer with better selectivity estimates
Improving MariaDB’s Query Optimizer with better selectivity estimates
 
JSON Support in MariaDB: News, non-news and the bigger picture
JSON Support in MariaDB: News, non-news and the bigger pictureJSON Support in MariaDB: News, non-news and the bigger picture
JSON Support in MariaDB: News, non-news and the bigger picture
 
ANALYZE for Statements - MariaDB's hidden gem
ANALYZE for Statements - MariaDB's hidden gemANALYZE for Statements - MariaDB's hidden gem
ANALYZE for Statements - MariaDB's hidden gem
 
MariaDB 10.4 - что нового
MariaDB 10.4 - что новогоMariaDB 10.4 - что нового
MariaDB 10.4 - что нового
 
MariaDB Optimizer - further down the rabbit hole
MariaDB Optimizer - further down the rabbit holeMariaDB Optimizer - further down the rabbit hole
MariaDB Optimizer - further down the rabbit hole
 
Lessons for the optimizer from running the TPC-DS benchmark
Lessons for the optimizer from running the TPC-DS benchmarkLessons for the optimizer from running the TPC-DS benchmark
Lessons for the optimizer from running the TPC-DS benchmark
 
MariaDB 10.3 Optimizer - where does it stand
MariaDB 10.3 Optimizer - where does it standMariaDB 10.3 Optimizer - where does it stand
MariaDB 10.3 Optimizer - where does it stand
 
MyRocks in MariaDB | M18
MyRocks in MariaDB | M18MyRocks in MariaDB | M18
MyRocks in MariaDB | M18
 
New Query Optimizer features in MariaDB 10.3
New Query Optimizer features in MariaDB 10.3New Query Optimizer features in MariaDB 10.3
New Query Optimizer features in MariaDB 10.3
 
MyRocks in MariaDB
MyRocks in MariaDBMyRocks in MariaDB
MyRocks in MariaDB
 
Histograms in MariaDB, MySQL and PostgreSQL
Histograms in MariaDB, MySQL and PostgreSQLHistograms in MariaDB, MySQL and PostgreSQL
Histograms in MariaDB, MySQL and PostgreSQL
 
Say Hello to MyRocks
Say Hello to MyRocksSay Hello to MyRocks
Say Hello to MyRocks
 
Common Table Expressions in MariaDB 10.2
Common Table Expressions in MariaDB 10.2Common Table Expressions in MariaDB 10.2
Common Table Expressions in MariaDB 10.2
 
MyRocks in MariaDB: why and how
MyRocks in MariaDB: why and howMyRocks in MariaDB: why and how
MyRocks in MariaDB: why and how
 
Эволюция репликации в MySQL и MariaDB
Эволюция репликации в MySQL и MariaDBЭволюция репликации в MySQL и MariaDB
Эволюция репликации в MySQL и MariaDB
 
Common Table Expressions in MariaDB 10.2 (Percona Live Amsterdam 2016)
Common Table Expressions in MariaDB 10.2 (Percona Live Amsterdam 2016)Common Table Expressions in MariaDB 10.2 (Percona Live Amsterdam 2016)
Common Table Expressions in MariaDB 10.2 (Percona Live Amsterdam 2016)
 
MariaDB 10.1 - что нового.
MariaDB 10.1 - что нового.MariaDB 10.1 - что нового.
MariaDB 10.1 - что нового.
 

Percona live-2012-optimizer-tuning

  • 1. Improving MySQL/MariaDB query performance through optimizer tuning Sergey Petrunya Timour Katchaounov {psergey,timour}@montyprogram.com
  • 2. What is an optimizer and why do I need to tune it 2 03:14:28 PM
  • 3. Why does one need optimizer tuning • Database performance is affected by many factors • One of them is the query optimizer (and query executor, together query processor) 3 03:14:28 PM
  • 4. What is a query optimizer ? • Converts SQL into execution instructions • Like GPS gives driving directions from A to B +----+--------------------+----------+--------+-----------------------------------+--------------+---- | id | select_type | table | type | possible_keys | key | key +----+--------------------+----------+--------+-----------------------------------+--------------+---- | 1 | PRIMARY | part | ALL | PRIMARY | NULL | NUL | 1 | PRIMARY | partsupp | ref | PRIMARY,i_ps_partkey,i_ps_suppkey | i_ps_partkey | 4 | 1 | PRIMARY | supplier | eq_ref | PRIMARY,i_s_nationkey | PRIMARY | 4 | 1 | PRIMARY | nation | eq_ref | PRIMARY,i_n_regionkey | PRIMARY | 4 | 1 | PRIMARY | region | ALL | PRIMARY | NULL | NUL | 2 | DEPENDENT SUBQUERY | partsupp | ref | PRIMARY,i_ps_partkey,i_ps_suppkey | i_ps_partkey | 4 | 2 | DEPENDENT SUBQUERY | supplier | eq_ref | PRIMARY,i_s_nationkey | PRIMARY | 4 | 2 | DEPENDENT SUBQUERY | nation | eq_ref | PRIMARY,i_n_regionkey | PRIMARY | 4 | 2 | DEPENDENT SUBQUERY | region | eq_ref | PRIMARY | PRIMARY | 4 +----+--------------------+----------+--------+-----------------------------------+--------------+---- 4 03:14:28 PM
  • 5. Can the optimizer work poorly? Yes, just like your GPS It can produce a sub-optimal query plan (compare to GPS's directions) Unneeded CPU/IO usage in the database is the same extra mileage How can one tell if they have a problem in query optimizer? • Like with the GPS: • Knowledge of possible routes This is what this tutorial is about • Common sense 5 03:14:28 PM
  • 6. Start from a database performance problem • Database response time is too high • or the server denies connections altogether • because running queries have occupied all @@max_connections threads. • Monitoring (cacti, etc) shows high CPU/ disk utilization • Is this an optimizer problem? • Maybe. 6 03:14:29 PM
  • 7. Signs that it's caused by the optimizer • Only certain kinds of queries are affected • Unlike concurrency/disk/binlog problems that cause slowdown for all queries • These queries can be caught with • Slow query log • pt-query-digest • SHOW PROCESSLIST • SHOW PROFILE 7 03:14:29 PM
  • 8. Optimizer problems in Slow query log • Percona server/MariaDB: --log_slow_verbosity=query_plan # Thread_id: 1 Schema: dbt3sf10 QC_hit: No # Query_time: 2.452373 Lock_time: 0.000113 Rows_sent: 0 Rows_examined: 1500000 # Full_scan: Yes Full_join: No Tmp_table: No Tmp_table_on_disk: No # Filesort: No Filesort_on_disk: No Merge_passes: 0 SET timestamp=1333385770; select * from customer where c_acctbal < -1000; • Look for big Query_time (>> Lock_time) • Look for Rows_examined >> Rows_sent • Although there are “legitimate” cases for this • Query_plan members (violet), if present, also show *possible* problem areas 8 03:14:29 PM
  • 9. SHOW PROCESSLIST; MariaDB [dbt3sf10]> show processlist; +----+------+-----------+----------+---------+------+---------------+-------- | Id | User | Host | db | Command | Time | State | Info +----+------+-----------+----------+---------+------+---------------+-------- | 1 | root | localhost | dbt3sf10 | Query | 176 | Sending data | select | 2 | root | localhost | dbt3sf10 | Query | 0 | NULL | show pr | 3 | root | localhost | dbt3sf10 | Query | 19 | freeing items | insert | 4 | root | localhost | dbt3sf10 | Sleep | 77 | | NULL | 5 | root | localhost | dbt3sf10 | Query | 4 | Updating | update | 6 | root | localhost | dbt3sf10 | Query | 5 | statistics | select • States to look for: • optimizing • executing • Statistics • Sending data 9 03:14:29 PM
  • 10. SHOW PROFILE • Uses the same set of states • Shows where exactly the query has spent time mysql> show profile for query 1; +--------------------+-----------+ | Status | Duration | +--------------------+-----------+ | starting | 0.000097 | | Opening tables | 0.000038 | Phases that can occupy lots of time: | System lock | 0.000014 | | Table lock | 0.000019 | Storage engine: transaction start, etc | init | 0.000027 | | optimizing | 0.000029 | Optimization (search for query plan) | statistics | 0.000036 | Optimization and sometimes InnoDB locking | preparing | 0.000023 | | executing | 0.000014 | Execution (of the query plan) | Sending data | 18.501940 | | end | 0.000015 | | query end | 0.000006 | | freeing items | 0.000029 | | logging slow query | 0.000004 | Storage engine: transaction commit, | logging slow query | 0.000050 | binary logging | cleaning up | 0.000006 | 10 03:14:29 PM +--------------------+-----------+
  • 11. Global observations summary • A query [pattern] that • Spends a lot of time in optimization/execution phase • Examines many more records than you think it should • Uses Full_scan, Full_join, etc • .. or all of the above • Is a candidate for further investigation 11 03:14:29 PM
  • 12. Reasons why a query is “heavy” • Some queries are inherently hard to compute • Some get a plan which looks good but turns out bad due to circumstances that were not accounted for • And some are just bad query plan choices 12 03:14:30 PM
  • 13. Know thy optimizer • How to tell which case you're dealing with? • Need to know “the distance” • The lower bound of amount of effort one needs to expend to arrive at the destination • Easy in geography, not so easy in SQL • Need to know “the roads” • Available table access strategies • How they are composed into query plans 13 03:14:30 PM
  • 14. Optimizer troubleshooting From simple to complex queries 14 03:14:30 PM
  • 15. Let's take a simple, slow query select * from orders where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506'; +----+-------------+--------+------+---------------+------+---------+------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+------+---------+------+----------+-------------+ | 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | NULL | 15084733 | Using where | +----+-------------+--------+------+---------------+------+---------+------+----------+-------------+ # Thread_id: 2 Schema: dbt3sf10 QC_hit: No # Query_time: 41.129178 Lock_time: 0.000174 Rows_sent: 2 Rows_examined: 15000000 # Full_scan: Yes Full_join: No Tmp_table: No Tmp_table_on_disk: No # Filesort: No Filesort_on_disk: No Merge_passes: 0 use dbt3sf10; SET timestamp=1333432984; select * from orders where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506'; • Full scan examined lots of rows • But only a few (two) of them “have contributed to the result set” • It was unnecessary to read the rest • Query plan is poor, it seems. 15 03:14:31 PM
  • 16. Status variables MariaDB [dbt3sf10]> flush status; Query OK, 0 rows affected (0.00 sec) MariaDB [dbt3sf10]> select * from orders where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506'; ... MariaDB [dbt3sf10]> show status like 'Handler%'; +----------------------------+----------+ | Variable_name | Value | +----------------------------+----------+ | Handler_commit | 1 | | Handler_delete | 0 | | Handler_discover | 0 | | Handler_icp_attempts | 0 | | Handler_icp_match | 0 | | Handler_mrr_init | 0 | | Handler_mrr_key_refills | 0 | | Handler_mrr_rowid_refills | 0 | | Handler_prepare | 0 | | Handler_read_first | 0 | | Handler_read_key | 0 | | Handler_read_next | 0 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | Indeed, 15M sequential reads | Handler_read_rnd_next | 15000001 | | Handler_rollback | 0 | | Handler_savepoint | 0 | | Handler_savepoint_rollback | 0 | | Handler_tmp_update | 0 | | Handler_tmp_write | 0 | | Handler_update | 0 | | Handler_write | 0 | +----------------------------+----------+ 23 rows in set (0.01 sec) 16 03:14:31 PM
  • 17. With index, we'll read fewer rows alter table orders add key i_o_orderdate (o_orderdate); select * from orders where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506'; +----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+ | 1 | SIMPLE | orders | ref | i_o_orderdate | i_o_orderdate | 4 | const | 6303 | Using where | +----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+ • The index allows to only read records with i_o_orderdate='Clerk..' • (At the cost of having to read index entries also) • Still not perfect: we're reading records that have the wrong o_orderDate • Let's check how it executes: 17 03:14:31 PM
  • 18. Status variables MariaDB [dbt3sf10]> flush status; MariaDB [dbt3sf10]> select * from orders where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506'; ... (0.39 sec) MariaDB [dbt3sf10]> show status like 'Handler%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_commit | 1 | | Handler_delete | 0 | | Handler_discover | 0 | | Handler_icp_attempts | 0 | | Handler_icp_match | 0 | | Handler_mrr_init | 0 | | Handler_mrr_key_refills | 0 | | Handler_mrr_rowid_refills | 0 | | Handler_prepare | 0 | | Handler_read_first | 0 | | Handler_read_key | 1 | 1 index lookup | Handler_read_next | Handler_read_prev | 6122 | | 0 | 6K forward index reads | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | | Handler_savepoint | 0 | | Handler_savepoint_rollback | 0 | | Handler_tmp_update | 0 | | Handler_tmp_write | 0 | | Handler_update | 0 | | Handler_write | 0 | +----------------------------+-------+ 23 rows in set (0.00 sec) 18 03:14:31 PM
  • 19. Can have an even better index alter table orders add key i_o_date_clerk (o_orderdate, o_clerk); select * from orders where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506'; +--+-----------+------+----+----------------------------+--------------+-------+-----------+----+---------------------+ |id|select_type|table |type|possible_keys |key |key_len|ref |rows|Extra | +--+-----------+------+----+----------------------------+--------------+-------+-----------+----+---------------------+ | 1|SIMPLE |orders|ref |i_o_orderdate,i_o_date_clerk|i_o_date_clerk|20 |const,const| 1|Using index condition| +--+-----------+------+----+----------------------------+--------------+-------+-----------+----+---------------------+ • Added a wide index covering both columns • Now, only records that match the whole WHERE will be read • Perfect! 19 03:14:31 PM
  • 20. Status variables MariaDB [dbt3sf10]> select * from orders where o_orderDate='1992-06-06' and o_clerk='Clerk#000009506'; ... (0.00 sec) MariaDB [dbt3sf10]> show status like 'Handler%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_commit | 1 | | Handler_delete | 0 | | Handler_discover | 0 | | Handler_icp_attempts | 0 | | Handler_icp_match | 0 | | Handler_mrr_init | 0 | | Handler_mrr_key_refills | 0 | | Handler_mrr_rowid_refills | 0 | | Handler_prepare | 0 | | Handler_read_first | 0 | | Handler_read_key | Handler_read_next | 1 | 0 | | 1 index lookup, and that's it | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | | Handler_savepoint | 0 | | Handler_savepoint_rollback | 0 | | Handler_tmp_update | 0 | | Handler_tmp_write | Handler_update | 0 | 0 | | • Let's try to generalize.. | Handler_write | 0 | +----------------------------+-------+ 23 rows in set (0.00 sec) 20 03:14:31 PM
  • 21. Index fundamentals ALTER TABLE tbl ADD INDEX(column1, column2, column3) On the physical level, On a logical level: an it's a B-TREE ordered directory 21 03:14:32 PM
  • 22. Querying data through index: 'ref' alter table orders add index(o_orderDATE, o_shippriority, o_custkey) o_orderDATE o_shipprio o_custkey • ref access can use any prefix: 2011-11-01 1 10 2011-11-02 1 10 o_orderDATE='2011-11-02' 2011-11-02 1 10 2011-11-02 1 11 2011-11-02 2 10 2011-11-03 2 10 o_orderDATE='2011-11-02' AND 2011-11-03 2 11 o_shippriority=2 2011-11-04 3 9 2011-11-04 4 10 2011-11-05 3 11 o_orderDATE='2011-11-08' AND 2011-11-06 1 10 2011-11-08 2 4 o_shippriority=3 AND o_custkey= 4 2011-11-09 1 10 • Not allowed: o_shipprioirity=2 o_shipprioirity=2 22 03:14:32 PM
  • 23. Ref(const) access summary • Uses equalities to make index lookups • #rows estimate is usually precise • ANALYZE will not help EXPLAIN: type=ref, ref=const, key_len +----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+ | 1 | SIMPLE | orders | ref | i_o_orderdate | i_o_orderdate | 4 | const | 6303 | Using where | +----+-------------+--------+------+---------------+---------------+---------+-------+------+-------------+ • Status variables: • 1 x Handler_read_key • N x Handler_read_next • Estimates and control.. will talk later 23 03:14:32 PM
  • 24. Range access 24 03:14:32 PM
  • 25. Range access • Can use equality and non-equality comparisons • Think of index as a directed axis. • and “ranges” on it where the WHERE condition is TRUE: … WHERE o_orderDATE > '2010-01-01' … WHERE o_orderDATE > '2010-01-01' AND o_orderDATE < '2010-01-07' 25 03:14:32 PM
  • 26. Range access (2) • Arbitrarily deep AND/OR formulas are supported • The optimizer will split out the “useful” parts of WHERE • And produce a list of disjoint ranges to be scanned: • “Useful” conditions compare key with constant: tbl.key>const tbl.key=const tbl.key BETWEEN c1 AND c2 tbl.key IS NULL tbl.key LIKE 'foo%' tbl.key IN (const1, const2 ...) 26 03:14:33 PM
  • 27. Range access for multi-part keys • Much more complex • Ranges over (keypart1, keypart2, …) tuple space • Think of it as axis with sub-axis at each point: • Range building rules: • Number of ranges is a function of WHERE, not of data in the table • Do not use knowledge about domain density (e.g. no knowledge like “there is no integers between 1 and 2”) 27 03:14:33 PM
  • 28. Multi-key-part range #1 • Equality on 1st key part? Can put condition on 2nd key part into sub-axis alter table orders add key i_o_date_clerk (o_orderdate, o_clerk); select * from orders where o_orderDATE IN ('2010-01-01', '2010-01-03') AND o_shippriority=1 28 03:14:33 PM
  • 29. Multi-key-part range #2 • This can cause big number of ranges. That's ok select * from orders where o_orderDATE IN ('2010-01-01', '2010-01-03') AND (o_shippriority =1 OR o_shippriority=2 OR o_shippriority=3) 29 03:14:33 PM
  • 30. Multi-key-part range #3 • Non-equality on 1st keypart: select * from orders where o_orderDATE >= '2010-01-01' AND o_shippriority>3 30 03:14:33 PM
  • 31. Multi-key-part range #3 • Non-equality on 1st keypart: select * from orders where o_orderDATE >= '2010-01-01' AND o_shippriority>3 • Can attach the second only to the edge “cell”: 31 03:14:34 PM
  • 32. Multi-key-part range summary • keyparts for which queries have equalities should go first • multiple keyparts with non-equality ranges will not reduce the number of rows read e.g. t.keypart1 BETWEEN … AND t.keypart2 BETWEEN … won't take advantage of condition on keypart2 • When keypartX is followed by other keyparts, then keypartX IN (1,... N) will work better than keypartX BETWEEN 1 AND N 32 03:14:34 PM
  • 33. Can I look at the ranges it has constructed? • Normally, no. But there is a last-resort method: • Get a really small subset of your data onto separate server • Get a debug build there. Start it with –debug • Run your query with FORCE INDEX: explain select * from orders force index(i_o_datepriokey) where (o_orderDATE IN ('2010-02-01','2010-02-03') AND o_shippriority < 3) OR (o_orderDATE >= '2010-03-10' AND o_shippriority < 4) • Go into mysqld.trace • Grep for “query:” until you find the query • Grep for “>print_quick” 33 03:14:34 PM
  • 34. Ranges in mysqld.trace mysqld.trace T@10 : | | query: explain select * from orders force index(i_o_datepriokey) where o_orderDATE IN ('2010-02-01','2010-02-03') AND o_shippriority < 3 OR (o_orderDATE > '2010-03-10' AND o _shippriority < 4) T@10 : | | >PROFILING::set_query_source T@10 : | | <PROFILING::set_query_source T@10 : | | >mysql_parse ... T@10 : | | | | | | | | | | >print_quick quick range select, key i_o_datepriokey, length: 9 2010-02-01/NULL < X < 2010-02-01/3 2010-02-03/NULL < X < 2010-02-03/3 2010-03-10 < X other_keys: 0x0: • EXPLAIN only shows key_len which is max # keyparts used +----+-------------+--------+-------+-----------------+-----------------+---------+------+------+-- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | E +----+-------------+--------+-------+-----------------+-----------------+---------+------+------+-- | 1 | SIMPLE | orders | range | i_o_datepriokey | i_o_datepriokey | 9 | NULL | 3 | U +----+-------------+--------+-------+-----------------+-----------------+---------+------+------+-- 34 03:14:34 PM
  • 35. Range access estimates +----+-------------+--------+-------+-----------------+-----------------+---------+------+------+-- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | E +----+-------------+--------+-------+-----------------+-----------------+---------+------+------+-- | 1 | SIMPLE | orders | range | i_o_datepriokey | i_o_datepriokey | 9 | NULL | 3 | U +----+-------------+--------+-------+-----------------+-----------------+---------+------+------+-- • Are obtained by doing dives into the index • Dive at the start of the range • Dive at the end • Estimate the difference • Usually are precise (not more than 2x wrong) • Are not affected/do not need ANALYZE TABLE • Cost you disk IO! • Indexes that are never worth using will still be probed! • Check your buffer pool + EXPLAINs to see if this happens! • Quick fix: IGNORE INDEX (unusable_index) 35 03:14:34 PM
  • 36. Range access: Status variables +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | Handler_commit | 1 | | Handler_delete | 0 | | Handler_discover | 0 | | Handler_icp_attempts | 9 | | Handler_icp_match | 9 | | Handler_mrr_init | 0 | | Handler_mrr_key_refills | 0 | | Handler_mrr_rowid_refills | 0 | | Handler_prepare | 0 | | Handler_read_first | Handler_read_key | 0 | 9 | | Increments | Handler_read_next | 9 | | Handler_read_prev | 0 | - index lookup counter | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | - walk-forward-in-index counter | Handler_rollback | 0 | | Handler_savepoint | 0 | | Handler_savepoint_rollback | 0 | | Handler_tmp_update | 0 | | Handler_tmp_write | 0 | | Handler_update | 0 | | Handler_write | 0 | +----------------------------+-------+ 23 rows in set (0.05 sec) 36 03:14:35 PM
  • 37. Index-only scans 37 03:14:35 PM
  • 38. Index-only scans • Besides lookups, indexes have another use • If the index has all columns you need, it is possible to avoid accessing table rows altogether • This is called “index only” scan • Can be applied to ref, and range accesses. 38 03:14:35 PM
  • 39. Index-only read example explain select sum(o_totalprice) from orders where o_orderdate between '1995-01-01' and '1995-01-07'; +--+-----------+------+-----+-------------+-------------+-------+----+----+---------------------+ |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra | +--+-----------+------+-----+-------------+-------------+-------+----+----+---------------------+ | 1|SIMPLE |orders|range|i_o_orderdate|i_o_orderdate|4 |NULL|4358|Using index condition| +--+-----------+------+-----+-------------+-------------+-------+----+----+---------------------+ alter table orders add key o_date_price (o_orderDATE, o_totalprice); # re-run the EXPLAIN: alter table orders add key o_date_price (o_orderDATE, o_totalprice); explain select sum(o_totalprice) from orders where o_orderdate between '1995-01-01' and '1995-02-01'; +--+-----------+------+-----+-------------+------------+-------+----+-----+------------------------+ |id|select_type|table |type |possible_keys|key |key_len|ref |rows |Extra | +--+-----------+------+-----+-------------+------------+-------+----+-----+------------------------+ | 1|SIMPLE |orders|range|... |o_date_price|4 |NULL|39424|Using where; Using index| +--+-----------+------+-----+-------------+------------+-------+----+-----+------------------------+ • scale=1, in-mem: from 0.2 to 0.1 sec 39 03:14:35 PM
  • 40. Index-only reads and status variables • Surprisingly, no difference select sum(o_totalprice) select sum(o_totalprice) from orders use index(o_date_price) from orders use index(i_o_orderdate) where o_orderdate between '1995-01-01' and where o_orderdate between '1995-01-01' and '1995-02-01'; '1995-02-01'; +----------------------------+-------+ +----------------------------+-------+ | Variable_name | Value | | Variable_name | Value | +----------------------------+-------+ +----------------------------+-------+ | Handler_commit | 1 | | Handler_commit | 1 | | Handler_delete | 0 | | Handler_delete | 0 | | Handler_discover | 0 | | Handler_discover | 0 | | Handler_icp_attempts | 0 | | Handler_icp_attempts | 20137 | | Handler_icp_match | 0 | | Handler_icp_match | 20137 | | Handler_mrr_init | 0 | | Handler_mrr_init | 0 | | Handler_mrr_key_refills | 0 | | Handler_mrr_key_refills | 0 | | Handler_mrr_rowid_refills | 0 | | Handler_mrr_rowid_refills | 0 | | Handler_prepare | 0 | | Handler_prepare | 0 | | Handler_read_first | 0 | | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_key | 1 | | Handler_read_next | 20137 | | Handler_read_next | 20137 | | Handler_read_prev | 0 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | | Handler_read_rnd_next | 0 | | Handler_rollback | 0 | | Handler_rollback | 0 | | Handler_savepoint | 0 | | Handler_savepoint | 0 | | Handler_savepoint_rollback | 0 | | Handler_savepoint_rollback | 0 | | Handler_tmp_update | 0 | | Handler_tmp_update | 0 | | Handler_tmp_write | 0 | | Handler_tmp_write | 0 | | Handler_update | 0 | | Handler_update | 0 | | Handler_write | 0 | | Handler_write | 0 | +----------------------------+-------+ 40 +----------------------------+-------+ 03:14:35 PM
  • 41. Index-only reads and status variables • Let's look at InnoDB “lower-level” counters show status like 'Innodb%'; +-----------------------------------+--------+ • These are global non FLUSHable | Variable_name | Value | counters, so we manually +-----------------------------------+--------+ ... calculate increment and show it | Innodb_buffer_pool_read_requests | 413168 | +68929 on the right | Innodb_buffer_pool_reads | 10034 | | Innodb_buffer_pool_wait_free | 0 | ... • _rows_read are the same, too | Innodb_rows_inserted | 0 | | Innodb_rows_read | Innodb_rows_updated | 181237 | +20137 | 0 | • _buffer_pool_reads are different +-----------------------------------+--------+ • A bit sense-less counting show status like 'Innodb%'; +-----------------------------------+--------+ • Have plans to fix in | Variable_name | Value | MariaDB. +-----------------------------------+--------+ ... | Innodb_buffer_pool_read_requests | 422426 | +2566 | Innodb_buffer_pool_reads | 10034 | | Innodb_buffer_pool_wait_free | 0 | ... | Innodb_rows_inserted | 0 | | Innodb_rows_read | 241651 | +20138 | Innodb_rows_updated | 0 | +-----------------------------------+--------+ 41 03:14:35 PM
  • 42. Index-only reads summary • Can be used with any index-based access methods • Allows to skip accessing table records • Some savings for CPU-bound loads • Huge savings for IO-bound loads • Extra columns in the index a cost, though • Index is bigger • [Potentially] more frequently updated • EXPLAIN shows “Using index” • Counting in for Handler_xxx and Innodb_row_reads status variables • Watch lower-level counters. 42 03:14:35 PM
  • 43. Index Condition Pushdown MySQL 5.6+, MariaDB 5.3+ 43 03:14:36 PM
  • 44. Index Condition Pushdown idea A non-index only read is a two-step process: 1. Read index 2. Read record 3. Check the WHERE condition. 44 03:14:36 PM
  • 45. Index Condition Pushdown idea Index Condition Pushdown 1. Read index 2. Check condition on index columns 3. Read record 4. Check the WHERE condition 45 03:14:36 PM
  • 46. Index condition pushdown example • Start without ICP alter table orders add key i_o_clerk(o_clerk); select o_clerk, o_orderkey, o_shippriority from orders where o_clerk IN ('Clerk#000000001', 'Clerk#00000201' , 'Clerk#000003001') and dayofweek(o_orderDATE)=1; # MariaDB 5.2.x/ MySQL 5.5.x +--+-----------+------+-----+-------------+---------+-------+----+----+-----------+ |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra | +--+-----------+------+-----+-------------+---------+-------+----+----+-----------+ | 1|SIMPLE |orders|range|i_o_clerk |i_o_clerk|16 |NULL|2978|Using where| +--+-----------+------+-----+-------------+---------+-------+----+----+-----------+ # MariaDB 5.3+, MySQL 5.6+ +--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+ |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra | +--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+ | 1|SIMPLE |orders|range|i_o_clerk |i_o_clerk|16 |NULL|2978|Using index condition; Using where| +--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+ 46 03:14:36 PM
  • 47. Counters alter table orders add key i_o_clerk(o_clerk); select o_clerk, o_orderkey, o_shippriority from orders where o_clerk IN ('Clerk#000000001', 'Clerk#00000201' , 'Clerk#000003001') and dayofweek(o_orderDATE)=1; # MariaDB 5.3+, MySQL 5.6+ +--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+ |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra | +--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+ | 1|SIMPLE |orders|range|i_o_clerk |i_o_clerk|16 |NULL|2978|Using index condition; Using where| +--+-----------+------+-----+-------------+---------+-------+----+----+----------------------------------+ +----------------------------+-------+ | Variable_name | Value | MariaDB [(none)]> show status like 'Innodb%'; +----------------------------+-------+ +----------------------------------+-------+ | Handler_commit | 1 | | Variable_name | Value | | Handler_delete | 0 | +----------------------------------+-------+ | Handler_discover | 0 | | Innodb_buffer_pool_read_requests | 32103 | + 24416 | Handler_icp_attempts | 2979 | | Innodb_rows_read | 3406 | + 2979 | Handler_icp_match | 2979 | | Handler_mrr_init | 0 | | Handler_mrr_key_refills | 0 | | Handler_mrr_rowid_refills | 0 | | Handler_prepare | 0 | | Handler_read_first | 0 | | Handler_read_key | 3 | | Handler_read_next | 2979 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | ... 47 03:14:36 PM
  • 48. Index condition pushdown example • Now, add an index that ICP could use: alter table orders add key i_o_clerk_date(o_clerk, o_orderDATE); select o_clerk, o_orderkey, o_shippriority from orders where o_clerk IN ('Clerk#000000001', 'Clerk#00000201' , 'Clerk#000003001') and dayofweek(o_orderDATE)=1; +--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+ |id|select_type|table |type |possible_keys |key |key_len|ref |rows|Extra | +--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+ | 1|SIMPLE |orders|range|i_o_clerk_date,i_o_clerk|i_o_clerk_date|16 |NULL|2978|Using index condition| +--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+ 48 03:14:36 PM
  • 49. Counters alter table orders add key i_o_clerk_date(o_clerk, o_orderDATE); select o_clerk, o_orderkey, o_shippriority from orders where o_clerk IN ('Clerk#000000001', 'Clerk#00000201' , 'Clerk#000003001') and dayofweek(o_orderDATE)=1; +--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+ |id|select_type|table |type |possible_keys |key |key_len|ref |rows|Extra | +--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+ | 1|SIMPLE |orders|range|i_o_clerk_date,i_o_clerk|i_o_clerk_date|16 |NULL|2978|Using index condition| +--+-----------+------+-----+------------------------+--------------+-------+----+----+---------------------+ +----------------------------+-------+ | Variable_name | Value | MariaDB [(none)]> show status like 'Innodb%'; +----------------------------+-------+ +-----------------------------------+-------+ | Handler_commit | 2 | | Variable_name | Value | | Handler_delete | 0 | +-----------------------------------+-------+ | Handler_discover | 0 | | Innodb_buffer_pool_read_requests | 7687 | +2585 | Handler_icp_attempts | 2979 | | Innodb_rows_read | 427 | +427 | Handler_icp_match | 427 | = 1/7th | Handler_mrr_init | 0 | | Handler_mrr_key_refills | 0 | | Handler_mrr_rowid_refills | 0 | | Handler_prepare | 0 | | Handler_read_first | 0 | | Handler_read_key | 3 | | Handler_read_next | 427 | = 1/7th | Handler_read_prev | 0 | | Handler_read_rnd | 0 | 49 03:14:37 PM
  • 50. Index Condition Pushdown optimization … or lack thereof alter table orders add key i_o_clerk_date(o_clerk, o_orderDATE); select o_clerk, o_orderkey, o_shippriority from orders where o_clerk IN ('Clerk#000000001', 'Clerk#00000201' , 'Clerk#000003001') and dayofweek(o_orderDATE)=1; INDEX (o_clerk) INDEX (o_clerk, o_orderDATE); • Range selectivity on 1st keypart is (or, should be) the same for both • Index entries are smaller • ICP is applicable • But is condition dayofweek(...)=1 selective? Current optimizer actions: • #1. Choose an index to use • #2. If possible, use IndexConditionPushdown with it • => Possibility of ICP doesn't affect index choice. 50 03:14:37 PM
  • 51. Index Condition Pushdown optimization … or lack thereof What to do, then? • USE/IGNORE INDEX hints • Create only indexes such that ICP will be used • Include approriate extra columns at the end • In our example: • Create INDEX (o_clerk, o_orderDATE) • Don't create INDEX (o_clerk) 51 03:14:37 PM
  • 52. Index Merge 52 03:14:37 PM
  • 53. Index Merge Union • ORed conditions on different columns cannot be resolved with any single index • Example: airline on-time performance data, 15M flights: select * from ontime where (Origin='SEA' or Dest='SEA'); Origin='SEA' Dest='SEA' 53 03:14:37 PM
  • 54. Index Merge union select * from ontime where (Origin='SEA' or Dest='SEA'); +--+-----------+------+-----------+-------------+-----------+-------+----+-----+-------------------------------------+ |id|select_type|table |type |possible_keys|key |key_len|ref |rows |Extra | +--+-----------+------+-----------+-------------+-----------+-------+----+-----+-------------------------------------+ | 1|SIMPLE |ontime|index_merge|Origin,Dest |Origin,Dest|6,6 |NULL|92850|Using union(Origin,Dest); Using where| +--+-----------+------+-----------+-------------+-----------+-------+----+-----+-------------------------------------+ • Optimization is challenging: OR Origin='SEA' Dest='SEA' v.s. ...no way to tell. Current assumtion: pessimistic (top) 54 03:14:37 PM
  • 55. Index merge: statistics counters select count(*) from ontime where (Origin='SEA' or Dest='SEA'); -- 46412 rows +----------------------------+--------+ | Variable_name | Value | +----------------------------+--------+ ... | Handler_read_first | 0 | | Handler_read_key | 2 | | Handler_read_next | 46412 | | Handler_read_prev | 0 | | Handler_read_rnd | 46412 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | ... | Innodb_rows_read | 464120 | +92824 select count(*) from ontime where Origin='SEA' -- 23207 rows +----------------------------+--------+ | Variable_name | Value | +----------------------------+--------+ ... | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_next | 23207 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_deleted | 0 | | Handler_read_rnd_next | 0 | ... | Innodb_rows_read | 556943 | +23207 1.5X reads look like 3x! 55 03:14:38 PM
  • 56. Index merge properties [MySQL, Percona, MariaDB 5.2x] index_merge plans are removed from consideration if a range access is possible: MySQL [ontime]> explain select * from ontime where (Origin='SEA' or Dest='SEA') and securitydelay=0; +--+-----------+------+----+-------------------------+-------------+-------+-----+------+-----------+ |id|select_type|table |type|possible_keys |key |key_len|ref |rows |Extra | +--+-----------+------+----+-------------------------+-------------+-------+-----+------+-----------+ | 1|SIMPLE |ontime|ref |Origin,Dest,SecurityDelay|SecurityDelay|5 |const|791546|Using where| +--+-----------+------+----+-------------------------+-------------+-------+-----+------+-----------+ MySQL [ontime]> explain select * from ontime where (Origin='SEA' or Dest='SEA') and depdelay < 12*60; +--+-----------+------+----+--------------------+----+-------+----+-------+-----------+ |id|select_type|table |type|possible_keys |key |key_len|ref |rows |Extra | +--+-----------+------+----+--------------------+----+-------+----+-------+-----------+ | 1|SIMPLE |ontime|ALL |Origin,DepDelay,Dest|NULL|NULL |NULL|1583093|Using where| +--+-----------+------+----+--------------------+----+-------+----+-------+-----------+ Solutions: • Upgrade to MariaDB 5.3+ :-) • IGNORE INDEX($index_used_for_range) • there is no “FORCE INDEX MERGE” hint 56 03:14:38 PM
  • 57. Index Merge/Union summary • Used for ORs spanning multiple indexes • Each part of OR must have an index it could use • Counter increments not quite comparable with single- index access methods • Can be turned off globally with set optimizer_switch='index_merge=off' • Can be turned off locally with IGNORE INDEX(some_merged_index) • Can be blocked by potential ref/range accesses • Fix1: Upgrade to MariaDB 5.3 • Fix2: IGNORE INDEX(other_range_indexes) USE INDEX (index_merge_indexes) 57 03:14:38 PM
  • 58. Index Merge/Intersection • Like Index Merge/union, but for ANDs • Aimed at cases when there is no composite index select avg(arrdelay) from ontime where depdel15=1 and OriginState ='CA'; +-----------+------+-----------+--------------------+--------------------+-------+----+-----+----------------------------------------- |select_type|table |type |possible_keys |key |key_len|ref |rows |Extra +-----------+------+-----------+--------------------+--------------------+-------+----+-----+----------------------------------------- |SIMPLE |ontime|index_merge|OriginState,DepDel15|OriginState,DepDel15|3,5 |NULL|76952|Using intersect(OriginState,DepDel15);Usi +-----------+------+-----------+--------------------+--------------------+-------+----+-----+----------------------------------------- • MySQL, MariaDB 5.2: only support equality conditions • MariaDB 5.3: any range condition supported • Must be manually enabled: set optimizer_switch='index_merge_sort_intersection=on' • EXPLAIN: Using sort-intersect 58 03:14:38 PM
  • 59. Index Merge/intersect optimization • Optimization challenges are similar to UNION select avg(arrdelay) from ontime where depdel15=1 and OriginState ='CA'; • There is no way to force index_merge/intersect • To disable • set optimizer_switch='index_merge_intersection=off • IGNORE INDEX(one_of_used_indexes) • Tip: showeling through lots of data? set optimizer_switch='index_merge_sort_intersection=on' set sort_buffer_size=... 59 03:14:38 PM
  • 60. JOINs 60 03:14:38 PM
  • 61. Consider a join select count(*) from customer, orders where c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT'; +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- | 1|SIMPLE |customer|range|... |c_acctbal |9 |NULL |6802|Using where; Using in | 1|SIMPLE |orders |ref |i_o_custkey |i_o_custkey|5 |customer.c_custkey| 7|Using where +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- 61 03:14:38 PM
  • 62. Nested loops join select count(*) from customer, orders where c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT'; +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- | 1|SIMPLE |customer|range|... |c_acctbal |9 |NULL |6802|Using where; Using in | 1|SIMPLE |orders |ref |i_o_custkey |i_o_custkey|5 |customer.c_custkey| 7|Using where +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- for each record R1 in customer { for each matching record R2 in orders { pass (R1, R2) into join output • EXPLAIN shows the loops, } from outer to inner } 62 03:14:39 PM
  • 63. Nested loops join select count(*) from customer, orders where c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT'; +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- | 1|SIMPLE |customer|range|... |c_acctbal |9 |NULL |6802|Using where; Using in | 1|SIMPLE |orders |ref |i_o_custkey |i_o_custkey|5 |customer.c_custkey| 7|Using where +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- for each record R1 in customer { if (where[customer] is satisfied) { for each matching record R2 in orders { • “Using where” means if (where[orders] is satisfied) pass (R1, R2) into join output checking a part of WHERE } • Which part? } } 63 03:14:39 PM
  • 64. Nested loops join select count(*) from customer, orders where c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT'; +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- | 1|SIMPLE |customer|range|... |c_acctbal |9 |NULL |6802|Using where; Using in | 1|SIMPLE |orders |ref |i_o_custkey |i_o_custkey|5 |customer.c_custkey| 7|Using where +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- for each record R1 in customer { if (where[customer] is satisfied) { for each matching record R2 in orders { • The part that refers to the if (where[orders] is satisfied) tables for which we know pass (R1, R2) into join output R{n}, the “current record”. } } } 64 03:14:39 PM
  • 65. Nested loops join select count(*) from customer, orders where c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT'; +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- | 1|SIMPLE |customer|range|... |c_acctbal |9 |NULL |6802|Using where; Using in | 1|SIMPLE |orders |ref |i_o_custkey |i_o_custkey|5 |customer.c_custkey| 7|Using where +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- for each record R1 in customer // 6802 loops { if (where[customer] is satisfied) { for each matching record R2 in orders // 7 loops { if (where[orders] is satisfied) pass (R1, R2) into join output • “rows” shows how many rows are read from each table } } • For `orders`, 7 rows will be read } 6802 times 65 03:14:39 PM
  • 66. Nested loops join select count(*) from customer, orders where c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT'; +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- | 1|SIMPLE |customer|range|... |c_acctbal |9 |NULL |6802|Using where; Using in | 1|SIMPLE |orders |ref |i_o_custkey |i_o_custkey|5 |customer.c_custkey| 7|Using where +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- for each record R1 in customer // 6802 loops { if (where[customer] is satisfied) { for each matching record R2 in orders // 7 loops { if (where[orders] is satisfied) • Wait, what about pass (R1, R2) into join output this 'if'? } } • It may reject some of } the 6802 values of R1 66 03:14:39 PM
  • 67. Nested loops join explain extended select * from customer, orders where c_custkey=o_custkey and c_acctbal < 1000 and o_orderpriority='1-URGENT'; --+-----------+--------+----+-------------+-----------+-------+------------------+------+--------+-----------+ id|select_type|table |type|possible_keys|key |key_len|ref |rows |filtered|Extra | --+-----------+--------+----+-------------+-----------+-------+------------------+------+--------+-----------+ 1|SIMPLE |customer|ALL |... |NULL |NULL |NULL |132987| 39.44|Using where| 1|SIMPLE |orders |ref |i_o_custkey |i_o_custkey|5 |customer.c_custkey| 7| 100.00|Using where| --+-----------+--------+----+-------------+-----------+-------+------------------+------+--------+-----------+ for each record R1 in customer // 6802 loops { if (where[customer] is satisfied) { for each matching record R2 in orders // 7 loops { if (where[orders] is satisfied) pass (R1, R2) into join output • “filtered” column shows the percentage of records that are } expected to pass the “if”. } } • 100% is the most frequent (optimizer puts 100% when there are no reliable estimates for filtering) 67 03:14:39 PM
  • 68. Ref access select count(*) from customer, orders where c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT'; +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- | 1|SIMPLE |customer|range|... |c_acctbal |9 |NULL |6802|Using where; Using in | 1|SIMPLE |orders |ref |i_o_custkey |i_o_custkey|5 |customer.c_custkey| 7|Using where +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- • `orders` is accessed with ref access: index lookups on index i_o_custkey using value of customer.c_custkey • This is similar to key=const ref access we've discussed earlier • But there is no “const”, the value of customer.c_custkey is different for each lookup. • Each lookup is expected to produce 7 matching rows. • This information is from Index Statistics. We'll cover it later. 68 03:14:39 PM
  • 69. How good a join can/should be 69 03:14:39 PM
  • 70. Let's collect some data select count(*) from customer, orders where c_custkey=o_custkey and c_acctbal < -500 and o_orderpriority='1-URGENT'; +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- | 1|SIMPLE |customer|range|... |c_acctbal |9 |NULL |6802|Using where; Using in | 1|SIMPLE |orders |ref |i_o_custkey |i_o_custkey|5 |customer.c_custkey| 7|Using where +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- [explain] select count(*) from customer; -- 150K [explain] select count(*) from customer where c_acctbal < -500 -- 6802 [explain] select count(*) from orders -- 1.5M [explain] select count(*) from orders where o_orderpriority='1-URGENT' – 300K One customer (c_custkey) – 7 orders • And use it to analyze the join: 70 03:14:40 PM
  • 71. Analyzing the join select count(*) from customer, orders where c_custkey=o_custkey and c_acctbal < 1000 and o_orderpriority='1-URGENT'; There are three ways to compute the join: • Yellow → Green • Blue → Green • Yellow, Blue 71 03:14:40 PM
  • 72. Join plan analysis +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- |id|select_type|table |type |possible_keys|key |key_len|ref |rows|Extra +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- | 1|SIMPLE |customer|range|... |c_acctbal |9 |NULL |6802|Using where; Using in | 1|SIMPLE |orders |ref |i_o_custkey |i_o_custkey|5 |customer.c_custkey| 7|Using where +--+-----------+--------+-----+-------------+-----------+-------+------------------+----+--------------------- • We're using the Yellow->Green read • The yellow part is read efficiently • Range access is used, it scans 6K rows • The green part read efficiently • ref access, 6K lookups, each producing 7 rows on average • This is probably better than the two other variants 72 03:14:40 PM
  • 73. Tracking query plan execution • EXPLAINs are what the optimizer *expects * to happen • What actually happens? • MySQL has no EXPLAIN ANALYZE • Possible solutions • Status variables • “userstat” • performance_schema? 73 03:14:40 PM
  • 74. Check status variables MariaDB [dbt3sf1]> show status like 'Handler%'; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ • Accesses to all tables are summed | Handler_commit | 1 | together! | Handler_delete | 0 | | Handler_discover | 0 | | Handler_icp_attempts | 0 | It is still possible to do analysis: • | Handler_icp_match | 0 | | Handler_mrr_init | Handler_mrr_key_refills | 0 | 0 | | • Run a “sub-join” with the 1st table; | Handler_mrr_rowid_refills | 0 | note the counters | Handler_prepare | 0 | | Handler_read_first | Handler_read_key | 0 | 6805 | | • Run a “sub-join” with the 1st and 2nd | Handler_read_next | 75951 | tables from the join order; | Handler_read_prev | 0 | | Handler_read_rnd | 0 | note the counters; | Handler_read_rnd_deleted | 0 | substract counters from #1 | Handler_read_rnd_next | 0 | | Handler_rollback | Handler_savepoint | 0 | 0 | | ...• ... • This is slow and painful. 74 03:14:40 PM
  • 75. More powerful: userstatv2 • Percona Server and MariaDB: MariaDB [dbt3sf1]> show table_statistics; +--------------+------------+-----------+--------------+-------------------------+ | Table_schema | Table_name | Rows_read | Rows_changed | Rows_changed_x_#indexes | +--------------+------------+-----------+--------------+-------------------------+ | dbt3sf1 | customer | 6805 | 0 | 0 | | dbt3sf1 | orders | 69147 | 0 | 0 | +--------------+------------+-----------+--------------+-------------------------+ 2 rows in set (0.00 sec) MariaDB [dbt3sf1]> show index_statistics; +--------------+------------+-------------+-----------+ | Table_schema | Table_name | Index_name | Rows_read | +--------------+------------+-------------+-----------+ | dbt3sf1 | customer | c_acctbal | 6805 | | dbt3sf1 | orders | i_o_custkey | 69147 | +--------------+------------+-------------+-----------+ 2 rows in set (0.00 sec) • Much better but self-joins are still painful 75 03:14:41 PM
  • 76. Recap • Ok we now know • Single table access methods • ref(const) • Range • index_merge • Nested loop join algorithm • EXPLAIN • How to check if execution follows EXPLAIN 76 03:14:41 PM
  • 77. Typical reasons of poor join performance • Poor access strategy for the 1st table • Highly-selective non-indexed conditions? Create an index! • Joins on non-equality conditions • e.g. “same day” joins on DATETIME columns • Tip: Convert to equality join at all costs • No index to do ref access for join • Cross-product joins inherently slow • Correlated data (...) • Wrong index statistics (...) 77 03:14:41 PM
  • 78. Non-uniform distributions o_orderdate • Some data is highly non- uniform Christmas • Performance depends on hitting the popular values Valentine's day • No one-for-all solution March, nn Jne , mm 78 03:14:41 PM
  • 79. Index and Table Statistics 79 03:14:41 PM
  • 80. Database statistics and plan stability (InnoDB/XtraDB) • Sample [8] random index leaf pages • Table statistics (stored) • rows - estimated number of rows in a table • clust_size - cluster index (table/primary key) size in number of pages • other_size - other index (non primary key) size in number of pages • Index statistics (stored) • fields - #fields in the index • rows_per_key - rows per 1 key value, per prefix fields ([1 column value], [2 columns value], [3 columns value], …) • index_total_pages – number of index pages • index_leaf_pages – number of leaf pages 80 03:14:41 PM
  • 81. Table statistics updates • Statistics updated when: • ANALYZE TABLE tbl_name [, tbl_name] … • SHOW TABLE STATUS, SHOW INDEX • Access to INFORMATION_SCHEMA.[TABLES| STATISTICS] • A table is opened for the first time (that is, after server restart) • A table has changed a lot (1/16th of the table updated/deleted/inserted) • When InnoDB Monitor is turned ON • Others (?) 81 03:14:41 PM
  • 82. Displaying statistics • MySQL 5.5, MariaDB 5.3, and older (GA versions) • Issue SQL statements to count rows/keys • Indirectly, look at EXPLAIN for simple queries • MariaDB 5.5, Percona Server 5.5 (using XtraDB) (GA versions) • information_schema.[innodb_index_stats, innodb_table_stats] • Read-only, always visible • MySQL 5.6 (development) • mysql.[innodb_index_stats, innodb_table_stats] • User updatetable • Only available if innodb_analyze_is_persistent=ON • MariaDB $NEXT (development) • MyISAM persistent updateable tables • + current MySQL and Percona mechanisms 82 03:14:42 PM
  • 83. Plan [in]stability • Statistics may vary a lot (orders) MariaDB [dbt3]> select * from information_schema.innodb_index_stats; +------------+-----------------+--------------+ +---------------+ | table_name | index_name | rows_per_key | | rows_per_key | error (actual) +------------+-----------------+--------------+ +---------------+ | partsupp | PRIMARY | 3, 1 | | 4, 1 | 25% | partsupp | i_ps_partkey | 3, 0 | => | 4, 1 | 25% (4) | partsupp | i_ps_suppkey | 64, 0 | | 91, 1 | 30% (80) | orders | i_o_orderdate | 9597, 1 | | 1660956, 0 | 99% (6234) | orders | i_o_custkey | 15, 1 | | 15, 0 | 0% (15) | lineitem | i_l_receiptdate | 7425, 1, 1 | | 6665850, 1, 1 | 99.9% (23477) +------------+-----------------+--------------+ +---------------+ MariaDB [dbt3]> select * from information_schema.innodb_table_stats; +-----------------+----------+ +----------+ | table_name | rows | | rows | +-----------------+----------+ +----------+ | partsupp | 6524766 | | 9101065 | 28% (8000000) | orders | 15039855 | ==> | 14948612 | 0.6% (15000000) | lineitem | 60062904 | | 59992655 | 0.1% (59986052) +-----------------+----------+ +----------+ 83 03:14:42 PM
  • 84. Controlling statistics (GA versions) • MySQL 5.5, MariaDB 5.3, and older • Manual tuning: optimizer hints, system variables • MySQL 5.5, MariaDB 5.5 with InnoDB plugin • innodb_stats_on_metadata = OFF – update only on restart, ANALYZE • innodb_stats_sample_pages = 8 is default - increase precision • No way to “freeze” db statistics in all cases • MariaDB 5.5, Percona Server 5.5 (using XtraDB) • Can “freeze” the current InnoDB statistics • innodb_use_sys_stats_table=ON – use I_S.INNODB_SYS_STATS • innodb_stats_auto_update=OFF – recalculate except for “first open” and “ANALYZE TABLE” • No manual control over statistics, only refresh by random sampling 84 03:14:42 PM
  • 85. Plan [in]stability • Same query, different statistics => • Different access methods and/or • Different JOIN orders => different query plans • Query performance may change a lot when statistics changes • BEWARE WHEN BENCHMARKING THE OPTIMIZER 85 03:14:42 PM
  • 86. Controlling statistics (MySQL dev) MySQL 5.6 (development version, public code) • Persistent and user-updatetable InnoDB statistics • innodb_analyze_is_persistent = ON, • updated only on ANALYZE TABLE • Control the precision of sampling [default 8] • innodb_stats_persistent_sample_pages, • innodb_stats_persistent_sample_pages • No new statistics compared to older versions 86 03:14:42 PM
  • 87. Controlling statistics (MariaDB dev) MariaDB $NEXT (development version, public code) • Current MySQL and Percona InnoDB statistics + • Engine-independent, persistent, user-updateable statistics • Precise • Additional statistics per column (even when there is no index): • min_value, max_value: minimum/maximum value per column • nulls_ratio: fraction of null values in a column • avg_length: average size of values in a column • avg_frequency: average number of rows with the same value => better query plans Code: bzr branch lp:~maria-captains/maria/maria-5.5-mwl248 87 03:14:42 PM
  • 88. Resources on InnoDB statistics From the InnoDB/XtraDB developers: https://mysqlperformanceblog.com/doc/percona-server/5.5/diagnostics/innodb_stats.html http://dev.mysql.com/doc/refman/5.6/en/innodb-other-changes-statistics-estimation.html http://dev.mysql.com/doc/refman/5.6/en/innodb-parameters.html http://dev.mysql.com/doc/refman/5.6/en/innodb-performance.html#innodb-persistent-stats http://blogs.innodb.com/wp/2011/04/innodb-persistent-statistics-at-last/ http://dev.mysql.com/doc/refman/5.6/en/analyze-table.html 88 03:14:42 PM
  • 89. Optimizer differences between MySQL branches 89 03:14:42 PM
  • 90. Optimizer in different MySQL branches • Base version: MySQL 5.5 • Optimizer is not different from MySQL 5.1 • Percona Server X.Y • Closely follows MySQL X.Y • Some improvements in diagnostics (eg. userstats) • MariaDB 5.3 • Lots of new features • MariaDB 5.5 • For the most part, is a merge of Maria-5.3 with MySQL-5.5 • But has a couple of small improvements • MySQL 5.6 • Lots of new features, intersection with MariaDB 5.3 90 03:14:43 PM
  • 91. Optimizer feature comparison (1) Feature MariaDB MySQL 5.5 MySQL 5.3/5.5 (5.6 dev) Disk access optimizations Index Condition Pushdown (ICP) YES - YES Disk-sweep Multi-range read (DS-MRR) YES - YES DS-MRR with Key-ordered retrieval YES - - Index_merge / Sort_intersection YES - - Cost-based choice of range vs. index_merge YES - - ORDER BY .. LIMIT <small_limit> - - YES Use extended (hidden) primary keys for innodb/xtradb YES (5.5) - - Join optimizations Batched key access (BKA) YES - YES Block hash join YES - - User-set memory limits on all join buffers YES - - Apply early outer table ON conditions YES - - Null-rejecting conditions tested early for NULLs YES - - 91 03:14:43 PM
  • 92. Optimizer feature comparison (2) Feature MariaDB MySQL MySQL 5.5 5.5 (5.6 dev) Subquery optimizations In-to-exists YES YES YES Semi-join YES - -YES Materialization YES - YES NULL-aware Materialization YES - - Cost choice of materialization vs in-to-exists YES - - Subquery cache YES - - Fast explain with subqueries YES - Optimizations for derived tables / views Delayed materialization of derived tables / materialized views YES - YES Instant EXPLAIN for derived tables YES - YES Derived Table with Keys optimization YES - YES Fields of merge-able views and derived tables used in YES - - equality optimizations 92 03:14:43 PM
  • 93. Optimizer feature comparison (3) Feature MariaDB MySQL 5.5 MySQL 5.3/5.5 (5.6 dev) Execution control LIMIT ROWS EXAMINED rows_limit YES (5.5) - - Optimizer control (optimizer switch) Systematic control of all optimizer strategies YES - partial EXPLAIN improvements Explain for DELETE, INSERT, REPLACE, and UPDATE - - YES EXPLAIN in JSON format - - YES More detailed and consistent EXPLAIN for subqueries YES - - 93 03:14:43 PM
  • 94. Optimizer switch (2) MariaDB 5.5 MySQL 5.6 Index merge: Index merge: index_merge=on, index_merge=on, index_merge_union=on, index_merge_union=on, index_merge_sort_union=on, index_merge_sort_union=on, index_merge_intersection=on, index_merge_intersection=on index_merge_sort_intersection=off Condition pushdown: Condition pushdown: engine_condition_pushdown=off, engine_condition_pushdown=on, index_condition_pushdown=on, index_condition_pushdown=on, mrr=off, mrr=on, mrr_cost_based=off, mrr_cost_based=on mrr_sort_keys=off 94 03:14:43 PM
  • 95. Optimizer switch (2) MariaDB 5.5 MySQL 5.6 Subqueries: Subqueries: materialization=on, materialization=on, semijoin=on, semijoin=on, loosescan=on, loosescan=on, firstmatch=on, firstmatch=on in_to_exists=on, partial_match_rowid_merge=on, partial_match_table_scan=on, subquery_cache=on, Derived tables: derived_merge=on, derived_with_keys=on 95 03:14:43 PM
  • 96. Optimizer switch (3) MariaDB 5.5 MySQL 5.6 Joins Joins: join_cache_bka=on, batched_key_access=off join_cache_incremental=on, block_nested_loop=on, join_cache_hashed=on, outer_join_with_cache=on, semijoin_with_cache=on, optimize_join_buffer_size=off + other system variables Other features table_elimination=on, extended_keys=off 96 03:14:43 PM
  • 97. Thank You! Q&A 97 03:14:44 PM