handler状态参数

mysql> show global status like '%handler%';
+----------------------------+------------+
| Variable_name | Value |
+----------------------------+------------+
| Handler_commit | 26193722 |
| Handler_delete | 142125 |
| Handler_discover | 0 |
| Handler_external_lock | 32309758 |
| Handler_mrr_init | 0 |
| Handler_prepare | 1636072 |
| Handler_read_first | 211046 |
| Handler_read_key | 174345074 |
| Handler_read_last | 2597 |
| Handler_read_next | 240098234 |
| Handler_read_prev | 223 |
| Handler_read_rnd | 32443997 |
| Handler_read_rnd_next | 7469027328 |
| Handler_rollback | 189 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 1362134 |
| Handler_write | 719730388 |
+----------------------------+------------+
Handler_savepoint 执行savepoint的语句数量
savepoint 语法
SAVEPOINT identifier
ROLLBACK [WORK] TO [SAVEPOINT] identifier
RELEASE SAVEPOINT identifier

在事务中可以设置保存点生成当前事务快照,在当前事务中通过rollback可恢复到任一保存点那一时刻的数据

测试:连续插入三条数据然后删除然后再插入,每个query执行后设置一个保存点

select * from t1;
+----+------+------+------+
| id | col1 | col2 | col3 |
+----+------+------+------+
| 2 | 3 | 5 | NULL |
| 4 | 4 | 5 | NULL |
| 5 | 6 | 5 | 1 |
| 8 | 8 | 5 | 2 |
| 9 | NULL | NULL | NULL |
+----+------+------+------+
5 rows in set (0.00 sec) 06:41:48[test](;)> insert into t1 values();
Query OK, 1 row affected (0.01 sec) 06:42:06[test](;)> savepoint ch_in1;
Query OK, 0 rows affected (0.00 sec) 06:42:13[test](;)> insert into t1 values();
Query OK, 1 row affected (0.00 sec) 06:42:15[test](;)> savepoint ch_in2;
Query OK, 0 rows affected (0.00 sec) 06:42:17[test](;)> insert into t1 values();
Query OK, 1 row affected (0.00 sec) 06:42:19[test](;)> savepoint ch_in3;
Query OK, 0 rows affected (0.00 sec) 06:42:21[test](;)> select * from t1;
+----+------+------+------+
| id | col1 | col2 | col3 |
+----+------+------+------+
| 2 | 3 | 5 | NULL |
| 4 | 4 | 5 | NULL |
| 5 | 6 | 5 | 1 |
| 8 | 8 | 5 | 2 |
| 9 | NULL | NULL | NULL |
| 10 | NULL | NULL | NULL |
| 11 | NULL | NULL | NULL |
| 12 | NULL | NULL | NULL |
+----+------+------+------+
8 rows in set (0.00 sec) 06:42:26[test](;)> delete from t1 where col1 is null;
Query OK, 4 rows affected (0.00 sec) 06:42:49[test](;)> savepoint ch_de1;
Query OK, 0 rows affected (0.00 sec) 06:42:54[test](;)> insert into t1 values();
Query OK, 1 row affected (0.00 sec) 06:43:01[test](;)> savepoint ch_in4;
Query OK, 0 rows affected (0.00 sec) 06:43:06[test](;)> select * from t1;
+----+------+------+------+
| id | col1 | col2 | col3 |
+----+------+------+------+
| 2 | 3 | 5 | NULL |
| 4 | 4 | 5 | NULL |
| 5 | 6 | 5 | 1 |
| 8 | 8 | 5 | 2 |
| 13 | NULL | NULL | NULL |
+----+------+------+------+
5 rows in set (0.00 sec) 06:43:10[test](;)> rollback to ch_in3;
Query OK, 0 rows affected (0.00 sec) 06:44:12[test](;)> select * from t1;
+----+------+------+------+
| id | col1 | col2 | col3 |
+----+------+------+------+
| 2 | 3 | 5 | NULL |
| 4 | 4 | 5 | NULL |
| 5 | 6 | 5 | 1 |
| 8 | 8 | 5 | 2 |
| 9 | NULL | NULL | NULL |
| 10 | NULL | NULL | NULL |
| 11 | NULL | NULL | NULL |
| 12 | NULL | NULL | NULL |
+----+------+------+------+
8 rows in set (0.00 sec) 06:44:13[test](;)> rollback to ch_in2;
Query OK, 0 rows affected (0.00 sec) 06:44:25[test](;)> select * from t1;
+----+------+------+------+
| id | col1 | col2 | col3 |
+----+------+------+------+
| 2 | 3 | 5 | NULL |
| 4 | 4 | 5 | NULL |
| 5 | 6 | 5 | 1 |
| 8 | 8 | 5 | 2 |
| 9 | NULL | NULL | NULL |
| 10 | NULL | NULL | NULL |
| 11 | NULL | NULL | NULL |
+----+------+------+------+
7 rows in set (0.00 sec) 06:44:27[test](;)> rollback to ch_in1;
Query OK, 0 rows affected (0.00 sec)

可以从后往前恢复到最早的保存点,但是如果跳过中间的保存点一步恢复到最早的恢复点则中点保存点将会自动删除

  • Handler_mrr_init 使用存储引擎自己的Multi-Range Read访问表的次数

    如 select no_second_index_col fro mrr_test where second_key_col=x; 使用mrr后会先根据二级索引条件查询出主键pk,然后将pk在read_rnd_buffer_size中进行排序,然后根据排序后的pk返回基表区数据,因为数据时按主键的顺序存放的,相邻的数据在同一个page中,所以发生的是顺io

    通过optimizer_switch来控制是否开启mrr,其中默认mrr_cost_based=on表示基于cost选择是否使用mrr,若设置为off则表示总是使用mrr

    07:16:25[test](;)> explain select col3 from mrr where sk>200 and sk<2000;
    +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+----------------------------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+----------------------------------+
    | 1 | SIMPLE | mrr | NULL | range | idx_sk | idx_sk | 5 | NULL | 222 | 100.00 | Using index condition; Using MRR |
    +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+----------------------------------+
    1 row in set, 1 warning (0.00 sec)

    MRR 适用于以下两种情况:1、 range access ; 2、 ref and eq_ref access, when they are using Batched Key Access

  • Handler_external_lock 调用external_lock() 函数的次数,当打开和关闭表时会调用此函数

    通常发生在扫描表的前后,不同的存储引可能返回的值不同,比如用于发现在锁发生之前有多少分区表会用到,然后对其进行lock

    #  for innodb
    CREATE TABLE `part` (
    `fname` varchar(50) NOT NULL,
    `lname` varchar(50) NOT NULL,
    `region_code` tinyint(3) unsigned NOT NULL,
    `dob` date NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    /*!50100 PARTITION BY RANGE ( region_code)
    (PARTITION p0 VALUES LESS THAN (64) ENGINE = InnoDB,
    PARTITION p1 VALUES LESS THAN (128) ENGINE = InnoDB,
    PARTITION p2 VALUES LESS THAN (192) ENGINE = InnoDB,
    PARTITION p3 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
    09:51:29[test](;)> select * from part;
    +-------+-------+-------------+------------+
    | fname | lname | region_code | dob |
    +-------+-------+-------------+------------+
    | aa | bb | 125 | 2017-09-15 |
    | aa | bb | 130 | 2017-09-15 |
    | aa | bb | 128 | 2017-09-15 |
    | aa | bb | 130 | 2017-09-15 |
    | aa | bb | 192 | 2017-09-15 |
    | aa | bb | 200 | 2017-09-15 |
    +-------+-------+-------------+------------+ 09:53:31[test](;)> explain select * from part where region_code > 125 AND region_code < 2000;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | 1 | SIMPLE | part | p1,p2,p3 | ALL | NULL | NULL | NULL | NULL | 6 | 16.67 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.01 sec) 09:53:32[test](;)> show status like 'Handler_external_lock';
    +-----------------------+-------+
    | Variable_name | Value |
    +-----------------------+-------+
    | Handler_external_lock | 2 |
    +-----------------------+-------+
    1 row in set (0.00 sec) # for myisam
    09:53:49[test](;)> explain select * from part_myisam where region_code > 125 AND region_code < 2000;
    +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | 1 | SIMPLE | part_myisam | p1,p2,p3 | ALL | NULL | NULL | NULL | NULL | 6 | 16.67 | Using where |
    +----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec) 09:53:59[test](;)> show status like 'Handler_external_lock';
    +-----------------------+-------+
    | Variable_name | Value |
    +-----------------------+-------+
    | Handler_external_lock | 8 |
    +-----------------------+-------+
    1 row in set (0.00 sec)
  • Handler_read_first 读取索引中第一个条目的次数,如果此值高,则说明全索引扫描操作频繁

    当发生全表扫描或全索引扫描时此值都会加1

  • Handler_read_key 通过索引读取一行的请求数,如果此值高,则表示索引使用得当;

    当走索引时此值加1是正常的,但是在对下面Handler_read_rnd_next相关测试时,当执行全表扫描时此值也加1

    06:00:22[test](;)> flush status
    -> ;
    Query OK, 0 rows affected (0.10 sec) 06:01:52[test](;)> select * from t1 where col1>=3 and col1<=5;
    +----+------+------+------+
    | id | col1 | col2 | col3 |
    +----+------+------+------+
    | 2 | 3 | 5 | NULL |
    | 4 | 4 | 5 | NULL |
    +----+------+------+------+
    2 rows in set (0.00 sec) 06:01:58[test](;)> show status like 'handler_read%';
    +-----------------------+-------+
    | Variable_name | Value |
    +-----------------------+-------+
    | Handler_read_first | 0 |
    | Handler_read_key | 1 |
    | Handler_read_last | 0 |
    | Handler_read_next | 2 |
    | Handler_read_prev | 0 |
    | Handler_read_rnd | 0 |
    | Handler_read_rnd_next | 0 |
    +-----------------------+-------+
    7 rows in set (0.00 sec)
    06:02:01[test](;)> explain select * from t1 where col1>=3 and col1<=5;
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
    | id | select_type | table | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra                 |
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
    |  1 | SIMPLE      | t1    | NULL       | range | udk_col1      | udk_col1 | 5       | NULL |    2 |   100.00 | Using index condition |
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
    1 row in set, 1 warning (0.01 sec)
  • Handler_read_last 读取索引中最后一个key的请求数,order by操作下,server会发布a first-key request followed by several next-key requests,order by desc下server会发布a last-key request followed by several previous-key requests
  • Handler_read_prev 按索引的顺序读取前面的行的请求数量,这种读取方式是优化order by ...desc 的主要方式

    如走索引扫描且倒叙排序,Handler_read_last会+1,Handler_read_prev会+读取的行数;如果加limit n限制,Handler_read_prev则+ n-1

    05:11:41[test](;)> explain select col1 from t1 order by col1 desc;
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
    | 1 | SIMPLE | t1 | NULL | index | NULL | udk_col1 | 5 | NULL | 6 | 100.00 | Using index |
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
    05:11:40[test](;)> show status like 'handler_read%';
    +-----------------------+-------+
    | Variable_name         | Value |
    +-----------------------+-------+
    | Handler_read_first    | 0     |
    | Handler_read_key      | 1     |
    | Handler_read_last     | 1     |
    | Handler_read_next     | 0     |
    | Handler_read_prev     | 6     |
    | Handler_read_rnd      | 0     |
    | Handler_read_rnd_next | 0     |
    +-----------------------+-------+
    05:24:52[test](;)> explain select col2 from t1 order by col2 desc limit 4;
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | t1    | NULL       | index | NULL          | idx_col2 | 5       | NULL |    4 |   100.00 | Using index |
    +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
    05:26:49[test](;)> show status like 'handler_read%';
    +-----------------------+-------+
    | Variable_name         | Value |
    +-----------------------+-------+
    | Handler_read_first    | 0     |
    | Handler_read_key      | 1     |
    | Handler_read_last     | 1     |
    | Handler_read_next     | 0     |
    | Handler_read_prev     | 3     |
    | Handler_read_rnd      | 0     |
    | Handler_read_rnd_next | 0     |
    +-----------------------+-------+

    如果是索引扫描加filesort则依然会视为全表扫描而增加相关状态值,如下,因为联合索引中的col3是无序的,索引以col3排序,需要全索引扫描后再排序一次

    05:40:58[test](;)> explain select col3 from t1 order by col3 desc limit 2;
    +----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------------+
    | 1 | SIMPLE | t1 | NULL | index | NULL | udk_col2_col3 | 10 | NULL | 6 | 100.00 | Using index; Using filesort |
    +----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------------+
    05:44:45[test](;)> show status like 'handler_read%';
    +-----------------------+-------+
    | Variable_name | Value |
    +-----------------------+-------+
    | Handler_read_first | 1 |
    | Handler_read_key | 1 |
    | Handler_read_last | 0 |
    | Handler_read_next | 0 |
    | Handler_read_prev | 0 |
    | Handler_read_rnd | 0 |
    | Handler_read_rnd_next | 7 |
    +-----------------------+-------+
    7 rows in set (0.00 sec)
  • Handler_read_next 按索引的顺序读取下一行的请求数,发生在有条件索引扫描,或无条件全索引扫描

  走索引扫描,Handler_read_next增加的值为索引扫描行数

03:21:52[test](;)> explain select * from t1 where col1>8;
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | t1 | NULL | range | udk_col1 | udk_col1 | 5 | NULL | 2 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
03:22:28[test](;)> select * from t1 where col1>8;
+----+------+------+------+
| id | col1 | col2 | col3 |
+----+------+------+------+
| 9 | 9 | 9 | 9 |
| 10 | 10 | 10 | 10 |
+----+------+------+------+
2 rows in set (0.00 sec) 03:22:31[test](;)> show status like 'handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 2 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+
7 rows in set (0.00 sec) 06:48:50[test](;)> explain select id from t1;  
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key    | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | index | NULL          | idx_id | 4       | NULL |    6 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+ +-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_first    | 1     |
| Handler_read_key      | 1     |
| Handler_read_last     | 0     |
| Handler_read_next     | 6     |
| Handler_read_prev     | 0     |
| Handler_read_rnd      | 0     |
| Handler_read_rnd_next | 0    

  但是如果扫描的只有一行,则不会增加此值

  

06:11:22[test](;)> select * from t1 where col1>=3 and col1<=3;
+----+------+------+------+
| id | col1 | col2 | col3 |
+----+------+------+------+
| 2 | 3 | 5 | NULL |
+----+------+------+------+
1 row in set (0.00 sec) 06:11:25[test](;)> show status like 'handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+
06:00:06[test](;)> explain select * from t1 where col1>=3 and col1<=3;
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | t1 | NULL | range | udk_col1 | udk_col1 | 5 | NULL | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
  • Handler_read_rnd 基于固定位置读取一行的请求数,如果query需要对结果集排序则此值会很高,全表扫描和join未用索引都会导致此值很高
  • Handler_read_rnd_next 从数据文件中读取下一行的请求数,全表扫描绘导致此值很高

   当发生全表扫描时,Handler_read_rnd_next会增加 表行数+1,1是指文件结束符,也会计数,读到eof表示已经扫描完成

02:33:43[test](;)> flush status
-> ;
Query OK, 0 rows affected (0.10 sec) 02:51:11[test](;)> show status like 'handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 0 |
| Handler_read_last | 0 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+
7 rows in set (0.00 sec) 02:51:16[test](;)> select * from t1;
+----+------+------+------+
| id | col1 | col2 | col3 |
+----+------+------+------+
| 2 | 3 | 5 | NULL |
| 4 | 4 | 5 | NULL |
| 5 | 6 | 5 | 1 |
| 8 | 8 | 5 | 2 |
| 9 | 9 | 9 | 9 |
| 10 | 10 | 10 | 10 |
+----+------+------+------+
6 rows in set (0.00 sec) 02:51:29[test](;)> show status like 'handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 1 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 7 |
+-----------------------+-------+
7 rows in set (0.00 sec)

MySQL table open cache status

  • Table_open_cache_hits 从打开的表缓存中查询的命中数
  • Table_open_cache_misses 从打开的表缓存中查询的未命中数
  • Table_open_cache_overflows 打开表缓存的溢出次数。这是在打开或关闭表后,缓存实例具有未使用的条目并且实例的大小大于table_open_cache / table_open_cache_instances的次数。

Mysql open files

  • open_files 文件打开的数量,指server打开的常规的文件,不包括其他类型的文件如sock文件和pip文件,也不包括存储引擎使用自己内部函数而不是通知server打开的文件

mysql temporary object

什么情况下差生临时表

1、union

01:11:45[test](;)> explain  select * from t2  union select * from t2 ;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 10 | 100.00 | NULL |
| 2 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 10 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec) 01:11:34[test](;)> explain select * from t2 union all select * from t2 ;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | PRIMARY | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 10 | 100.00 | NULL |
| 2 | UNION | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 10 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)

2、group by

第一个语句是不通过索引排序就借用临时表进行;第二个通过order by null避免排序;第三个直接通过索引进行分组

03:10:20[test](;)> explain select * from t1 ignore index(udk_col2_col3,idx_col2) group by col2;
+----+-------------+-------+------------+------+------------------------+------+---------+------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+------------------------+------+---------+------+------+----------+---------------------------------+
| 1 | SIMPLE | t1 | NULL | ALL | udk_col2_col3,idx_col2 | NULL | NULL | NULL | 2 | 100.00 | Using temporary; Using filesort |
+----+-------------+-------+------------+------+------------------------+------+---------+------+------+----------+---------------------------------+
1 row in set, 1 warning (0.00 sec) 03:20:25[test](;)> explain select * from t1 ignore index(udk_col2_col3,idx_col2) group by col2 order by null;
+----+-------------+-------+------------+------+------------------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+------------------------+------+---------+------+------+----------+-----------------+
| 1 | SIMPLE | t1 | NULL | ALL | udk_col2_col3,idx_col2 | NULL | NULL | NULL | 2 | 100.00 | Using temporary |
+----+-------------+-------+------------+------+------------------------+------+---------+------+------+----------+-----------------+
1 row in set, 1 warning (0.00 sec) 03:20:37[test](;)> explain select * from t1 group by col2;
+----+-------------+-------+------------+-------+------------------------+----------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+------------------------+----------+---------+------+------+----------+-------+
| 1 | SIMPLE | t1 | NULL | index | udk_col2_col3,idx_col2 | idx_col2 | 5 | NULL | 2 | 100.00 | NULL |
+----+-------------+-------+------------+-------+------------------------+----------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

3、distinct和order by非同一字段

如下第一个语句,因为col3非前缀索引字段,是无序的,所以全索引扫描去重后还需通过临时表再进行一次排序;第二个排序字段col2是联合索引的前缀字段所以可以通过全索引扫描一次性完成去重和排序

03:30:34[test](;)> explain select distinct col2,col3 from t1 order by col3 ;
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+----------------------------------------------+
| 1 | SIMPLE | t1 | NULL | index | udk_col2_col3 | udk_col2_col3 | 10 | NULL | 2 | 100.00 | Using index; Using temporary; Using filesort |
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+----------------------------------------------+
1 row in set, 1 warning (0.00 sec) 03:30:37[test](;)> explain select distinct col2,col3 from t1 order by col2 ;
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | index | udk_col2_col3 | udk_col2_col3 | 10 | NULL | 2 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

4、join 中排序字段所属表不是驱动表

18:29:17[test](;)> explain select * from t1 join t2 using(id) order by t2.col2;
+----+-------------+-------+------------+------+---------------+--------+---------+------------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+--------+---------+------------+------+----------+---------------------------------+
| 1 | SIMPLE | t1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 6 | 100.00 | Using temporary; Using filesort |
| 1 | SIMPLE | t2 | NULL | ref | idx_id | idx_id | 5 | test.t1.id | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+--------+---------+------------+------+----------+---------------------------------+
2 rows in set, 1 warning (0.00 sec
# 避免
explain select * from t1 force index(PRIMARY) join t2 force index(idx_col2) on t1.id=t2.id order by t2.col2;        
+----+-------------+-------+------------+--------+---------------+----------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys | key      | key_len | ref        | rows | filtered | Extra       |
+----+-------------+-------+------------+--------+---------------+----------+---------+------------+------+----------+-------------+
|  1 | SIMPLE      | t2    | NULL       | index  | NULL          | idx_col2 | 5       | NULL       |   13 |   100.00 | Using where |
|  1 | SIMPLE      | t1    | NULL       | eq_ref | PRIMARY       | PRIMARY  | 4       | test.t2.id |    1 |   100.00 | NULL        |
+----+-------------+-------+------------+--------+---------------+----------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.01 sec

什么情况下产生磁盘临时表

1、当内存放不下时转储为磁盘临时表,内存临时表大小受以下两个参数限制,为16M

05:22:33[test](;)> show global variables like '%table%size%';
+---------------------+-----------+
| Variable_name | Value |
+---------------------+-----------+
| max_heap_table_size | 134217728 |
| tmp_table_size | 134217728 |
+---------------------+-----------+
05:22:56[test](;)> select 134217728/1024/1024/8;
+-----------------------+
| 134217728/1024/1024/8 |
+-----------------------+
| 16.000000000000 |
+-----------------------+
1 row in set (0.00 sec)

2、memory 存储引擎不支持text或blob字段类型,所以创建磁盘临时表

3、是用union时,如果字符串列中字符串长度有超过512(二进制单位为byte,非二进制单位为字符)

01:48:01[test](;)> select char_length(name) from t3 where id=2;
+-------------------+
| char_length(name) |
+-------------------+
|               976 |
+-------------------+
1 row in set (0.00 sec)

01:45:28[test](;)> select * from t3 union select * from t3;

01:45:34[test](;)> show status like '%tmp%';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 1     |
| Created_tmp_files       | 0     |
| Created_tmp_tables      | 1     |
+-------------------------+-------+
3 rows in set (0.00 sec)

优化:union all不会进行去重,无序临时表;对于分组去重尽量走索引,如果无法走索引则尽量select必须的字段;尽量不要使用text和blob数据类型

mysql select type

表示的是表连接查询的类型,包括以下几种类型

Select_full_join

表示全为全表扫描的join查询次数;此值如果较高就需要查看连接字段上是否创建了合适的索引

Select_full_range_join

关联表上走范围查询的join查询次数;可以看到以下select_scan 也加1,select_scan表示驱动表走全表扫描的join查询次数,所以加1

14:18:03[test](;)> explain select * from t1 join t2 on t2.id<7;
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------------------------------------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 1 | SIMPLE | t2 | NULL | range | idx_id | idx_id | 5 | NULL | 3 | 100.00 | Using index condition; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------------------------------------------+ 14:24:14[test](;)> show status like 'select%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| Select_full_join | 0 |
| Select_full_range_join | 1 |
| Select_range | 0 |
| Select_range_check | 0 |
| Select_scan | 1 |
+------------------------+-------+

Select_scan

Select_range

表示驱动表走范围查询的join查询次数及单表走范围查询的次数,如

01:48:10[test](;)> explain select * from t1 where col2>=1 and col2<=4;
+----+-------------+-------+------------+-------+------------------------+---------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+------------------------+---------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | t1 | NULL | range | udk_col2_col3,idx_col2 | udk_col2_col3 | 5 | NULL | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+------------------------+---------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec) 02:01:39[test](;)> explain select * from t1 join t2 on t1.id=t2.id where t2.id>=1 and t1.id<=7;
+----+-------------+-------+------------+-------+----------------+---------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+----------------+---------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | t1 | NULL | range | PRIMARY,idx_id | PRIMARY | 4 | NULL | 4 | 100.00 | Using where |
| 1 | SIMPLE | t2 | NULL | ALL | idx_id | NULL | NULL | NULL | 10 | 70.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+-------+----------------+---------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
02:16:20[test](;)> show status like 'Select_range';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Select_range  | 2     |
+---------------+-------+
1 row in set (0.00 sec

Select_range_check

常出现在非等式条件的连接查询中,下述的执行过程是遍历t1表,然后求t2表中的id<=t1.id的记录,在这一步无法得知是走全表扫描快还是走t2.idx_id索引快,所以需要在确定t1.id后才能够基于cost选择是否走索引执行第二步

14:16:57[test](;)> desc t1;
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| col1 | int(11) | YES | UNI | NULL | |
| col2 | int(11) | YES | MUL | NULL | |
| col3 | int(11) | YES | | NULL | |
+-------+---------+------+-----+---------+----------------+
4 rows in set (0.00 sec) 14:17:37[test](;)> desc t2;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| col1 | varchar(18) | YES | MUL | NULL | |
| id | int(11) | YES | MUL | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
14:15:26[test](;)> explain select * from t1,t2 where t1.id>=t2.id;
+----+-------------+-------+------------+------+----------------+------+---------+------+------+----------+------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+----------------+------+---------+------+------+----------+------------------------------------------------+
| 1 | SIMPLE | t1 | NULL | ALL | PRIMARY,idx_id | NULL | NULL | NULL | 6 | 100.00 | NULL |
| 1 | SIMPLE | t2 | NULL | ALL | idx_id | NULL | NULL | NULL | 10 | 33.33 | Range checked for each record (index map: 0x2) |
+----+-------------+-------+------------+------+----------------+------+---------+------+------+----------+------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
# 去除t2表上id上的索引则不走range check
总结:主要关注的值为Select_full_join ,如若不为0则需要检查程序中的join语句

mysql sorts

需要着重关注的参数是sort_scan,表示走全表扫描排序的次数

13:18:29[test](;)> explain select * from t1   order by col3;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)

但是走索引也可能会伴随着文件排序,如下所示,是排序列字段在索引中无序导致的

13:17:59[test](;)> explain select * from t1 where col2>=1 and col2<=4 order by col3;
+----+-------------+-------+------------+-------+------------------------+---------------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+------------------------+---------------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | t1 | NULL | range | udk_col2_col3,idx_col2 | udk_col2_col3 | 5 | NULL | 1 | 100.00 | Using index condition; Using filesort |
+----+-------------+-------+------------+-------+------------------------+---------------+---------+------+------+----------+---------------------------------------+
1 row in set, 1 warning (0.00 sec)

MySQL table lock

官方文档:https://dev.mysql.com/doc/refman/5.6/en/internal-locking.html

mysql 对 MyISAMMEMORY, 和MERGE表使用表锁机制,分为读锁和写锁,如当session1会变更myisam 表t时,会加table write lock,在语句执行完之后才会释放表锁,在此期间其他会话对表t的读或写都会进入排队等候阶段

模拟表锁

 CREATE TABLE `t3` (
  `id` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 # session 1
mysql> show status like 'table_lock%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 136 |
| Table_locks_waited | 1 |
+-----------------------+-------+
2 rows in set (0.00 sec) mysql> insert into test.t3 values(1);
Query OK, 1 row affected (5.00 sec)

同一时间开启另一个会话对t3表进行变更操作,因为磁盘io性能问题才可以测试出表锁等待(30测试机)

#session2
mysql> insert into test.t3 values(1);
Query OK, 1 row affected (6.04 sec)

session2会等待session1执行完insert语句后才会进行插入,会看到所等待次数加1

mysql> show status like 'table_lock%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 138 |
| Table_locks_waited | 2 |
+-----------------------+-------+

process states

需要关注的状态为:

Creating sort index :表示mysql正在使用内部临时表来进行排序

Copying to tmp table on disk :将临时表copy到磁盘,性能很差

Sending data :发送数据到客户端,如果时间较长,则有可能是通过磁盘扫描大量的数据造成的;避免select *、通过limit减少网络流量传输;

Searching rows for update : mysql正在通过索引寻找待更新的记录,正常情况下使瞬间完成,此值应该为0,如果较高则说明索引的选择性不高过滤性较差,则会造成频繁的锁等待及CPU飙高

Waiting for table metadata lock :ddl被dml阻塞或阻塞dml,所以表结构及数据类型尽量一次性设计好,不要常做变动

session 1
01:27:50[test](;)> begin
-> ;
Query OK, 0 rows affected (0.00 sec) 01:27:53[test](;)> update t1 set col4=2 where col4=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
session 2
01:29:13[test](;)> alter table t1 drop col4;
01:28:35[test](;)> show full processlist;
+-------+------+-----------+------+---------+------+---------------------------------+--------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-------+------+-----------+------+---------+------+---------------------------------+--------------------------+
| 87859 | root | localhost | test | Query | 0 | starting | show full processlist |
| 87866 | root | localhost | test | Sleep | 1503 | | NULL |
| 87887 | root | localhost | test | Query | 3 | Waiting for table metadata lock | alter table t1 drop col4 |
| 89762 | pmm | localhost | NULL | Sleep | 0 | | NULL |
+-------+------+-----------+------+---------+------+---------------------------------+--------------------------+

mysql row operations

innodb row lock time

innodb 支持行锁,上锁对象是索引记录,包括S row lock和X row lock,当事务进行update或执行select for update时会尝试在操作的行上获取X row lock,如果当前行上已经标记为有S(通过select in share mode 添加)或X存在,则会进入锁队列排队等待,X的优先级高于S

不同隔离级别锁行为也不同,当前只讨论RC隔离级别下的行锁行为(线上为RC),分析变更字段有无索引的行为

测试环境

08:00:24[test](;)> desc t1;
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| col1 | int(11) | YES | UNI | NULL | |
| col2 | int(11) | YES | MUL | NULL | |
| col3 | int(11) | YES | | NULL | |
| col4 | int(11) | YES | | NULL | |
+-------+---------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
08:11:12[test](;)> select * from t1;
+----+------+------+------+------+
| id | col1 | col2 | col3 | col4 |
+----+------+------+------+------+
|  1 |    1 |    1 |    1 |    1 |
|  2 |    2 |   10 |    2 |   10 |
|  3 |    3 |   10 |    3 |   10 |
+----+------+------+------+------+

主键索引

只在主键索引记录上加X row lock

update table set col4=2 where id=1;

---TRANSACTION 1978835, ACTIVE 5 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 8652, OS thread handle 139711496959744, query id 72597 localhost root cleaning up

二级索引

走二级索引col2变更数据,会在索引col2=10的记录及所对应的主键id=2和id=3的索引记录上加X row lock,总共是4个行锁

session 1

08:11:29[test](;)> begin;
Query OK, 0 rows affected (0.00 sec) 08:11:34[test](;)> update t1 set col4=11 where col2=10;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0 # 锁信息
---TRANSACTION 1979120, ACTIVE 6 sec
3 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 9861, OS thread handle 139711497225984, query id 81552 localhost root cleaning up
--------

开启另一会话尝试变更id=3的行,产生锁等待;session 2在等待主键索引上id=3上的X row lock

session 2

07:57:57[test](;)> begin;
Query OK, 0 rows affected (0.00 sec) 08:13:00[test](;)> update t1 set id=4 where id=3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction #锁信息
---TRANSACTION 1979159, ACTIVE 5 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 8856, OS thread handle 139711496693504, query id 82866 localhost root updating
update t1 set id=4 where id=3
------- TRX HAS BEEN WAITING 5 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 218 page no 3 n bits 72 index PRIMARY of table `test`.`t1` trx id 1979159 lock_mode X locks rec but not gap waiting
Record lock, heap no 6 PHYSICAL RECORD: n_fields 7; compact format; info bits 0
0: len 4; hex 80000003; asc ;;
1: len 6; hex 0000001e32f0; asc 2 ;;
2: len 7; hex 66000001c51353; asc f S;;
3: len 4; hex 80000003; asc ;;
4: len 4; hex 8000000a; asc ;;
5: len 4; hex 80000003; asc ;;
6: len 4; hex 8000000b; asc ;; ------------------
---TRANSACTION 1979120, ACTIVE 134 sec
3 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 9861, OS thread handle 139711497225984, query id 81552 localhost root cleaning up
--------

Innodb_row_lock_time为innodb 行锁等待时长;Innodb_row_lock_waits为产生所等待的次数

无索引

在id=1的主键记录上加X row lock,innodb先将所有行加X row lock 然后返回给server通过where条件判定,如果不符合条件则释放锁,所以最终只在符合条件的主键记录上加锁,但是也存在锁住全行的过程,所以也有可能产生死锁;

但是对于RR隔离级别,为了防止幻影读会在所有行上加X row lock

session 1

08:35:49[test](;)> begin;
Query OK, 0 rows affected (0.00 sec) 08:35:51[test](;)> update t1 set col4=2 where col4=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0 session 2 08:36:52[test](;)> update t1 set col4=11 where col4=10;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0
session 1

08:35:49[test](;)> begin;
Query OK, 0 rows affected (0.00 sec) 08:35:51[test](;)> update t1 set col4=2 where col4=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0 session 2 08:36:52[test](;)> update t1 set col4=11 where col4=10;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warning

建议:尽量使变更操作走索引;避免大事务可以有效减缓锁资源争用

icp(Index Condition Pushdown)

什么是ICP

mysql 存储引擎是mysql的一个组件,数据是存储引擎负责存储的,server和存储引擎进行交互取的数据,当使用索引时,ICP用于优化server读取数据的过程

如以下查询语句

 CREATE TABLE `icp_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`col1` varchar(20) DEFAULT NULL,
`col2` varchar(20) DEFAULT NULL,
`col3` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_col1_col2` (`col1`,`col2`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 05:31:31[test](;)> select * from icp_test;
+----+------+------+------+
| id | col1 | col2 | col3 |
+----+------+------+------+
| 1 | aa1 | bb1 | NULL |
| 2 | aa2 | bb2 | NULL |
| 3 | aa3 | bb3 | NULL |
| 4 | aa3 | cc1 | NULL |
| 5 | aa3 | cc2 | NULL |
| 6 | aa3 | cc3 | NULL |
| 7 | aa1 | dd1 | NULL |
| 8 | aa1 | dd2 | NULL |
+----+------+------+------+
8 rows in set (0.01 sec) 05:16:22[test](;)>select * from icp_test where col1='aa1' and col2 like '%b1';

如果没有使用ICP:因为是前缀模糊搜索,所以col2不走索引,innodb通过idx_col1_col2索引读取出col1='aa1'的行返回给server,server再根据条件筛选出col2 为'%b1'的记录

如果使用ICP:where 条件col1和col2上有联合索引,所以server将col2 的条件搜索也放到innodb层,innodb通过读取索引idx_col1_col2读取符合col1和col2条件key对应的主键然后回表取出完整的行返回给server;减少了innodb读取基表的次数以及server访问innodb的次数,降低物理I/O

使用ICP的表现

# 未使用ICP
05:31:48[test](;)> SET optimizer_switch = 'index_condition_pushdown=off';
Query OK, 0 rows affected (0.00 sec) 06:02:26[test](;)> explain select * from icp_test where col1='aa1' and col2 like '%b1';
+----+-------------+----------+------------+------+---------------+---------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+---------------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | icp_test | NULL | ref | idx_col1_col2 | idx_col1_col2 | 63 | const | 3 | 12.50 | Using where |
+----+-------------+----------+------------+------+---------------+---------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
06:05:39[test](;)> show status like 'handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 3 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+ # 使用ICP
05:16:22[test](;)> explain select * from icp_test where col1='aa1' and col2 like '%b1';
+----+-------------+----------+------------+------+---------------+---------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+---------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | icp_test | NULL | ref | idx_col1_col2 | idx_col1_col2 | 63 | const | 3 | 12.50 | Using index condition |
+----+-------------+----------+------------+------+---------------+---------------+---------+-------+------+----------+-----------------------+ 06:06:29[test](;)> show status like 'handler_read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 1 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+

使用ICP的条件

查询类型为 range、ref、eq_ref和ref_or_null

二级索引,因为ICP的目的是减少读取整个行记录的数量从而减少I/O操作,而走主键查询会将整行数据读取到buffer pool,ICP就变得没有意义;测试表中如果无col3列也不会走ICP,同样的道理,二级索引idx_col1_col2也包括主键的

Most Fragmented Tables by Freeable Size

展示的是information_schema.tables中的字段data_free,为数据文件碎片即不连续的section(8个连续的页为一个区),DBA需要关注

变更边长字段的值时会产生磁盘碎片,值越大说明碎片越多,data_free/data_length*100% 低于15%不会产生性能影响

查询语句

SELECT concat( table_schema, '.', table_name ) table_name, concat(round( data_free / ( 1024 *1024 ) , 2), 'M') data_free, concat( round( data_length / ( 1024 *1024 ) , 2 ) , 'M' ) data_length,  concat( round( index_length / ( 1024 *1024 ) , 2 ) , 'M' ) index_length,  concat( round( round( data_length + index_length ) / ( 1024 *1024 ) , 2 ) , 'M' ) total_size FROM information_schema.TABLES where TABLE_SCHEMA not in ('sys','mysql','test','information_schema','performance_schema') ORDER BY data_free DESC;      

Top Tables by Auto Increment Usage

使用率是通过auto_increment/字段最大限制值*100% 计算得来的,如

mysql> SHOW TABLE STATUS like 'biz_contacts'\G
*************************** 1. row ***************************
Name: biz_contacts
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 723929
Avg_row_length: 102
Data_length: 74072064
Max_data_length: 0
Index_length: 0
Data_free: 4194304
Auto_increment: 8636346196731166205
Create_time: 2017-07-12 00:11:03
Update_time: 2017-09-19 21:51:43
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options:
Comment: 联系信息表
1 row in set (0.00 sec) mysql> desc biz_contacts;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| ContactsID | bigint(20) | NO | PRI | NULL | auto_increment |
| LinkMan | varchar(64) | YES | | NULL | |
| AreaID | bigint(20) | YES | | NULL | |
| FullAddress | varchar(256) | YES | | NULL | |
| MobilePhone | varchar(64) | YES | | NULL | |
| Phone | varchar(64) | YES | | NULL | |
| Email | varchar(64) | YES | | NULL | |
| QQ | varchar(64) | YES | | NULL | |
| WeChat | varchar(64) | YES | | NULL | |
| PostCode | varchar(64) | YES | | NULL | |
| Position | varchar(64) | YES | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
11 rows in set (0.00 sec) mysql> select 8636346196731166205/9223372036854775807;
+-----------------------------------------+
| 8636346196731166205/9223372036854775807 |
+-----------------------------------------+
| 0.9364 |
+-----------------------------------------+
1 row in set (0.00 sec)
mysql> select ContactsID from qlw_nxin_com.biz_contacts limit 1;
+--------------------+
| ContactsID |
+--------------------+
| 636182262510132839 |
+--------------------+
1 row in set (0.00 sec)

上述展示的问题是自增值很快就会达到限制的最大值,到时表将不能有新的记录插入

建议:使用从1自增列作为主键,上述主键字段过长,很容易达到限制长度,自增值也会达到限制;过长的字段使用字符串类型

int的宽度问题

整形存储要求 :https://dev.mysql.com/doc/refman/5.7/en/integer-types.html         
int(3) 和int(11)的区别只在于存储时的宽度和存储占用的空间以及长度限制都没有关系,要想看出区别可以使用zerofill,如下所示

插入的值的位数如果小于设置的宽度则左边用0填充,如果是超过或等于设置的宽度则正常显示

mysql> create table t (t int(3) zerofill);
Query OK, 0 rows affected (0.00 sec) mysql> insert into t set t = 10;
Query OK, 1 row affected (0.00 sec) mysql> select * from t;
+——+
| t |
+——+
| 010 |
+——+
1 row in set (0.11 sec) Zerofill with default width, the same as int(10): mysql> create table t (t int zerofill);
Query OK, 0 rows affected (0.02 sec) mysql> insert into t set t = 10;
Query OK, 1 row affected (0.02 sec) mysql> select * from t;
+————+
| t |
+————+
| 0000000010 |
+————+
1 row in set (0.08 sec)

最新文章

  1. java jvm常用命令工具
  2. Atitit &#160;记录方法调用参数上下文arguments
  3. nagios的nrpe的check_command配置优化
  4. shell和bat 监控进程,自动关机
  5. Linux信号处理
  6. UVa 12627 (递归 计数 找规律) Erratic Expansion
  7. Magic Pairs - SGU 119(同余)
  8. linux环境下deb格式 转换成rpm格式
  9. 【初级坑跳跳跳】[NULLException] findViewById() id 引用错误,导致空指针
  10. 无废话WCF入门教程三[WCF的宿主]
  11. Android注解使用之Dagger2实现项目依赖关系解耦
  12. 快速开发 jQuery 插件的 10 大技巧
  13. 自动化安装DHCP配置脚本
  14. js中的单例模式
  15. NTP服务器搭建
  16. numpy中矩阵乘法,星乘(*)和点乘(.dot)的区别
  17. UDAF(用户自定义聚合函数)求众数
  18. A. 【UR #17】滑稽树上滑稽果
  19. Install vsftpd on centos
  20. mysql exists 如何使用

热门文章

  1. Fragment(一)--Fragment用法常见问题
  2. [windows]设置使用空白密码进行远程登录
  3. TFS数据库分离附加经验总结
  4. vuex存取token,http简单封装、模拟登入权限校验操作、路由懒加载的几种方式、vue单页设置title
  5. How to install Eclipse in linux
  6. 05_Python格式化打印
  7. codeforces Gym 100338F Spam Filter 垃圾邮件过滤器(模拟,实现)
  8. IOS7.1 企业应用 证书无效 已解决
  9. python基础一 day14 生成器函数进阶
  10. java B转换KB MB GB TB PB EB ZB