测试数据
CREATE TABLE forums
(
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
uid BIGINT DEFAULT ‘0‘ NOT NULL
COMMENT ‘发帖人ID‘,
title VARCHAR(100) DEFAULT ‘‘ NOT NULL
COMMENT ‘标题‘,
content MEDIUMTEXT NULL
COMMENT ‘内容‘,
create_ts BIGINT DEFAULT ‘0‘ NOT NULL
COMMENT ‘创建时间‘
)COMMENT ‘帖子‘ ENGINE = InnoDB CHARSET = utf8mb4;
调用存储过程插入1000000万条数据
CREATE PROCEDURE insert_test_data_forums(IN loops INT)
BEGIN
DECLARE v1 INT;
set v1 = loops;
WHILE v1 > 0 DO
INSERT INTO forums(uid, title, content, create_ts)
VALUES (floor(rand() * 1000), ‘test标题标题标题标题标题‘, ‘test内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容‘, unix_timestamp() + floor(3600 * 24 * rand()));
set v1 = v1 -1;
END WHILE;
END;
sql> CALL insert_test_data_forums(1000000)
[2018-03-10 13:03:50] 1 row affected in 3m 16s 82ms
一、count 查询
mysql> explain select count(*) from forums;
+----+-------------+--------+------------+-------+---------------+---------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | forums | NULL | index | NULL | PRIMARY | 8 | NULL | 890283 | 100.00 | Using index |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
mysql> select count(*) from forums;
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
1 row in set (1.25 sec)
mysql> show profiles;
+----------+------------+-------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+-------------------------------------+
| 1 | 0.01062300 | explain select count(*) from forums |
| 2 | 1.24545500 | select count(*) from forums |
+----------+------------+-------------------------------------+
2 rows in set, 1 warning (0.01 sec)
添加 uid 索引后重新执行count查询
mysql> alter table forums add index k_uid(uid);
Query OK, 0 rows affected (2.61 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show profiles;
+----------+------------+-----------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+-----------------------------------------+
| 1 | 0.01062300 | explain select count(*) from forums |
| 2 | 1.24545500 | select count(*) from forums |
| 3 | 2.60981400 | alter table forums add index k_uid(uid) |
+----------+------------+-----------------------------------------+
3 rows in set, 1 warning (0.00 sec)
mysql> explain select count(*) from forums;
+----+-------------+--------+------------+-------+---------------+-------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+---------------+-------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | forums | NULL | index | NULL | k_uid | 8 | NULL | 890283 | 100.00 | Using index |
+----+-------------+--------+------------+-------+---------------+-------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> select count(*) from forums;
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
1 row in set (0.15 sec)
mysql> show profiles;
+----------+------------+-----------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+-----------------------------------------+
| 1 | 0.01062300 | explain select count(*) from forums |
| 2 | 1.24545500 | select count(*) from forums |
| 3 | 2.60981400 | alter table forums add index k_uid(uid) |
| 4 | 0.00021000 | explain select count(*) from forums |
| 5 | 0.15110800 | select count(*) from forums |
+----------+------------+-----------------------------------------+
5 rows in set, 1 warning (0.00 sec)
对比之后可以看到添加uid索引后,count查询 mysql优化器选择的是uid索引 使用force index强制使用索引,可以发现使用k_uid索引的效率要比使用主键索引要快的多。此部分原因需要学习mysql B+树结构才能明白。
mysql> select count(*) from forums force index(primary);
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
1 row in set (1.23 sec)
mysql> select count(*) from forums force index(k_uid);
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
1 row in set (0.15 sec)
再次插入400万条数据 对比同样的count查询的性能
mysql> call test.insert_test_data_forums(4000000);
Query OK, 1 row affected (31 min 57.31 sec)
上面插入的时间很慢,估计如果先删除索引k_uid 然后重建索引 会快很多
mysql> explain select count(*) from forums;
+----+-------------+--------+------------+-------+---------------+-------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+---------------+-------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | forums | NULL | index | NULL | k_uid | 8 | NULL | 4451950 | 100.00 | Using index |
+----+-------------+--------+------------+-------+---------------+-------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
mysql> select count(*) from forums;
+----------+
| count(*) |
+----------+
| 5000000 |
+----------+
1 row in set (3.38 sec)
可以发现性能降低了20倍
二、limit查询
limit查询的问题:limit查询偏移量很大的时候,mysql会扫面很多行记录。
解决办法:通常有两种方式来解决limit查询的问题,第一种是使用索引覆盖扫描获取指定行再返回原表关联查询。另一种方法就是记录上一次偏移量的位置
mysql> explain select * from forums limit 300000,1;
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------+
| 1 | SIMPLE | forums | NULL | ALL | NULL | NULL | NULL | NULL | 4451950 | 100.00 | NULL |
+----+-------------+--------+------------+------+---------------+------+---------+------+---------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> select * from forums limit 300000,1;
1 row in set (0.90 sec)
可以看到次数mysql走的是全表扫描,用子查询延迟关联让mysql扫描更少的页面,再返回原表进行关联查询,大大提高性能。
```
mysql> explain select * from forums where id in (select id from forums limit 300000,1);
ERROR 1235 (42000): This version of MySQL doesn‘t yet support ‘LIMIT & IN/ALL/ANY/SOME subquery‘
mysql> explain select * from forums f inner join (select id from forums limit 300000,1) a on f.id = a.id ;
+----+-------------+------------+------------+--------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+---------+---------+------+---------+----------+-------------+
| 1 | PRIMARY |
mysql> select * from forums f inner join (select id from forums limit 300000,1) a on f.id = a.id ;
1 row in set (0.07 sec)