Mysql索引优化实战②

83 阅读4分钟

根据非主键字段排序的分页查询

# 一个违反直觉的查询,先使用index排序,再查出需要的数据.比直接使用文件排序快.

select * from employees ORDER BY name limit 90000,5;
+-------+------------+-------+----------+---------------------+
| id    | name       | age   | position | hire_time           |
+-------+------------+-------+----------+---------------------+
| 90999 | zhuge90996 | 90996 | dev      | 2023-09-10 11:24:22 |
| 91000 | zhuge90997 | 90997 | dev      | 2023-09-10 11:24:22 |
| 91001 | zhuge90998 | 90998 | dev      | 2023-09-10 11:24:22 |
| 91002 | zhuge90999 | 90999 | dev      | 2023-09-10 11:24:22 |
|    94 | zhuge91    |    91 | dev      | 2023-09-10 11:20:59 |
+-------+------------+-------+----------+---------------------+
5 rows in set (0.08 sec)

select * from employees e inner join (select id from employees order by name limit 90000,5) ed on e.id = ed.id;
+-------+------------+-------+----------+---------------------+-------+
| id    | name       | age   | position | hire_time           | id    |
+-------+------------+-------+----------+---------------------+-------+
| 90999 | zhuge90996 | 90996 | dev      | 2023-09-10 11:24:22 | 90999 |
| 91000 | zhuge90997 | 90997 | dev      | 2023-09-10 11:24:22 | 91000 |
| 91001 | zhuge90998 | 90998 | dev      | 2023-09-10 11:24:22 | 91001 |
| 91002 | zhuge90999 | 90999 | dev      | 2023-09-10 11:24:22 | 91002 |
|    94 | zhuge91    |    91 | dev      | 2023-09-10 11:20:59 |    94 |
+-------+------------+-------+----------+---------------------+-------+
5 rows in set (0.01 sec)


explain select * from employees ORDER BY name limit 90000,5;
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
|  1 | SIMPLE      | employees | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 100120 |   100.00 | Using filesort |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set, 1 warning (0.00 sec)


explain select * from employees e inner join (select id from employees order by name limit 90000,5) ed on e.id = ed.id;
+----+-------------+------------+------------+--------+---------------+-----------------------+---------+-------+-------+----------+-------------+
| id | select_type | table      | partitions | type   | possible_keys | key                   | key_len | ref   | rows  | filtered | Extra       |
+----+-------------+------------+------------+--------+---------------+-----------------------+---------+-------+-------+----------+-------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL    | NULL          | NULL                  | NULL    | NULL  | 90005 |   100.00 | NULL        |
|  1 | PRIMARY     | e          | NULL       | eq_ref | PRIMARY       | PRIMARY               | 4       | ed.id |     1 |   100.00 | NULL        |
|  2 | DERIVED     | employees  | NULL       | index  | NULL          | idx_name_age_position | 185     | NULL  | 90005 |   100.00 | Using index |
+----+-------------+------------+------------+--------+---------------+-----------------------+---------+-------+-------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)


Join关联查询优化

​ 小表驱动大表, 关联字段加索引, 强制指定驱动表

嵌套循环连接 Nested-Loop Join(NLJ) 算法

整个过程会读取 t2 表的所有数据(扫描100行)

然后遍历这每行数据中字段 a 的值,根据 t2 表中 a 的值索引扫描 t1 表 中的对应行(扫描100次 t1 表的索引,1次扫描可以认为最终只扫描 t1 表一行完整数据,也就是总共 t1 表也扫描了100 行)。因此整个过程扫描了 200 行。

基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法

整个过程对表 t1 和 t2 都做了一次全表扫描,因此扫描的总行数为10000(表 t1 的数据总量) + 100(表 t2 的数据总量) = 10100。

并且 join_buffer 里的数据是无序的,因此对表 t1 中的每一行,都要做 100 次判断,所以内存中的判断次数是 100 * 10000= 100 万次。

CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

create table t2 like t1;

drop procedure if exists insert_t1;
delimiter ;;
create procedure insert_t1()
begin
declare i int;
set i=1;
while(i<=10000)do
insert into t1(a,b) values(i,i);
set i=i+1;
end while;
end;;
delimiter ;
call insert_t1();


drop procedure if exists insert_t2;
delimiter ;;
create procedure insert_t2()
begin
declare i int;
set i=1;
while(i<=100)do
insert into t2(a,b) values(i,i);
set i=i+1;
end while;
end;;
delimiter ;
call insert_t2();

EXPLAIN select * from t1 inner join t2 on t1.a= t2.a;
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key       | key_len | ref               | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------------+------+----------+-------+
|  1 | SIMPLE      | t2    | NULL       | ALL  | idx_a_b_c     | NULL      | NULL    | NULL              |  100 |   100.00 | NULL  |
|  1 | SIMPLE      | t1    | NULL       | ref  | idx_a_b_c     | idx_a_b_c | 4       | explain_test.t2.a |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------------+------+----------+-------+

EXPLAIN select * from t1 inner join t2 on t1.b= t2.b;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  100 |   100.00 | NULL                                       |
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9930 |    10.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+

	


count(*)查询优化

常见优化方法

1、查询mysql自己维护的总行数 (对于innodb存储引擎的表mysql不会存储表的总记录行数,因为有MVCC机制)

2、show table status

3、将总数维护到Redis里

4、增加数据库计数表

阿里巴巴Mysql规范解读

选择正确的数据类型,对于性能至关重要

  • 数值类型

    • 如果整形数据没有负数,建议指定为UNSIGNED无符号类型。
    • 建议使用TINYINT代替ENUM、BITENUM、SET。
    • DECIMAL最适合保存准确度要求高,用于计算的数据。
    • 建议使用整形类型来运算和存储实数
  • 日期和时间

    • 用MySQL的内建类型DATE、TIME、DATETIME来存储时间,而不是使用字符串。
    • TIMESTAMP是UTC时间戳,与时区相关。
    • DATETIME的存储格式是一个YYYYMMDD HH:MM:SS的整数,与时区无关
  • 字符串

    • 字符串的长度相差较大用VARCHAR;字符串短,且所有值都接近一个长度用CHAR。

    • 尽量少用BLOB和TEXT,如果实在要用可以考虑将BLOB和TEXT字段单独存一张表,用id关联。

    • BLOB系列存储二进制字符串,与字符集无关。TEXT系列存储非二进制字符串,与字符集相关。