zoukankan      html  css  js  c++  java
  • MySQL索引优化

    索引优化

    1、单表索引优化

    单表索引优化分析

    范围值容易导致索引失效。

    创建表

    • 建表 SQL
    CREATE TABLE IF NOT EXISTS article(
    	id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    	author_id INT(10) UNSIGNED NOT NULL,
    	category_id INT(10) UNSIGNED NOT NULL,
    	views INT(10) UNSIGNED NOT NULL,
    	comments INT(10) UNSIGNED NOT NULL,
    	title VARCHAR(255) NOT NULL,
    	content TEXT NOT NULL
    );
    
    INSERT INTO article(author_id,category_id,views,comments,title,content)
    VALUES
    (1,1,1,1,'1','1'),
    (2,2,2,2,'2','2'),
    (1,1,3,3,'3','3');
    • 表中的测试数据
    mysql> SELECT * FROM article;
    +----+-----------+-------------+-------+----------+-------+---------+
    | id | author_id | category_id | views | comments | title | content |
    +----+-----------+-------------+-------+----------+-------+---------+
    |  1 |         1 |           1 |     1 |        1 | 1     | 1       |
    |  2 |         2 |           2 |     2 |        2 | 2     | 2       |
    |  3 |         1 |           1 |     3 |        3 | 3     | 3       |
    +----+-----------+-------------+-------+----------+-------+---------+
    3 rows in set (0.00 sec)

    查询案例

    • 查询category_id为1且comments 大于1的情况下,views最多的article_id。
    mysql> SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
    +----+-----------+
    | id | author_id |
    +----+-----------+
    |  3 |         1 |
    +----+-----------+
    1 row in set (0.00 sec)
    • 此时 article 表中只有一个主键索引
    mysql> SHOW INDEX FROM article;
    +---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | Table   | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
    +---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | article |          0 | PRIMARY  |            1 | id          | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
    +---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    1 row in set (0.00 sec)
    • 使用 explain 分析 SQL 语句的执行效率:EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
    mysql> EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
    +----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+
    | id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows | Extra                       |
    +----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+
    |  1 | SIMPLE      | article | ALL  | NULL          | NULL | NULL    | NULL |    3 | Using where; Using filesort |
    +----+-------------+---------+------+---------------+------+---------+------+------+-----------------------------+
    1 row in set (0.00 sec)
    • 结论:
      • 很显然,type是ALL,即最坏的情况。
      • Extra 里还出现了Using filesort,也是最坏的情况。
      • 优化是必须的。

    开始优化:新建索引

    • 创建索引的 SQL 命令
    # ALTER TABLE article ADD INDEX idx_article_ccv('category_id', 'comments', 'views'); 
    create index idx_article_ccv on article(category_id, comments, views);
    • 在 category_id 列、comments 列和 views 列上建立联合索引
    mysql> create index idx_article_ccv on article(category_id, comments, views);
    Query OK, 0 rows affected (0.01 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> SHOW INDEX FROM article;
    +---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | Table   | Non_unique | Key_name        | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
    +---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | article |          0 | PRIMARY         |            1 | id          | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
    | article |          1 | idx_article_ccv |            1 | category_id | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
    | article |          1 | idx_article_ccv |            2 | comments    | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
    | article |          1 | idx_article_ccv |            3 | views       | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
    +---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    4 rows in set (0.00 sec)
    • 再次执行查询:type变成了range,这是可以忍受的。但是extra里使用Using filesort仍是无法接受的。
    mysql> EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
    +----+-------------+---------+-------+-----------------+-----------------+---------+------+------+---------------------------------------+
    | id | select_type | table   | type  | possible_keys   | key             | key_len | ref  | rows | Extra                                 |
    +----+-------------+---------+-------+-----------------+-----------------+---------+------+------+---------------------------------------+
    |  1 | SIMPLE      | article | range | idx_article_ccv | idx_article_ccv | 8       | NULL |    1 | Using index condition; Using filesort |
    +----+-------------+---------+-------+-----------------+-----------------+---------+------+------+---------------------------------------+
    1 row in set (0.00 sec)
    • 分析:

      • 但是我们已经建立了索引,为啥没用呢?

      • 这是因为按照B+Tree索引的工作原理,先排序 category_id,如果遇到相同的 category_id 则再排序comments,如果遇到相同的 comments 则再排序 views。

      • 当comments字段在联合索引里处于中间位置时,因为comments>1条件是一个范围值(所谓 range),MySQL 无法利用索引再对后面的views部分进行检索,即 range 类型查询字段后面的索引无效

      • 将查询条件中的 comments > 1 改为 comments = 1 ,发现 Use filesort 神奇地消失了,从这点可以验证:范围后的索引会导致索引失效

    mysql> EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments = 1 ORDER BY views DESC LIMIT 1;
    +----+-------------+---------+------+-----------------+-----------------+---------+-------------+------+-------------+
    | id | select_type | table   | type | possible_keys   | key             | key_len | ref         | rows | Extra       |
    +----+-------------+---------+------+-----------------+-----------------+---------+-------------+------+-------------+
    |  1 | SIMPLE      | article | ref  | idx_article_ccv | idx_article_ccv | 8       | const,const |    1 | Using where |
    +----+-------------+---------+------+-----------------+-----------------+---------+-------------+------+-------------+
    1 row in set (0.00 sec)

    删除索引

    • 删除索引的 SQL 指令
    DROP INDEX idx_article_ccv ON article;
    • 删除刚才创建的 idx_article_ccv 索引
    mysql> DROP INDEX idx_article_ccv ON article;
    Query OK, 0 rows affected (0.00 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> SHOW INDEX FROM article;
    +---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | Table   | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
    +---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | article |          0 | PRIMARY  |            1 | id          | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
    +---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    1 row in set (0.00 sec)

    再次创建索引

    • 创建索引的 SQL 指令
    # ALTER TABLE article ADD INDEX idx_article_ccv('category_id',  'views'); 
    create index idx_article_ccv on article(category_id, views);
    • 由于 range 后(comments > 1)的索引会失效,这次我们建立索引时,直接抛弃 comments 列,先利用 category_id 和 views 的联合索引查询所需要的数据,再从其中取出 comments > 1 的数据(我觉着应该是这样的)
    mysql> create index idx_article_ccv on article(category_id, views);
    Query OK, 0 rows affected (0.30 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> SHOW INDEX FROM article;
    +---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | Table   | Non_unique | Key_name        | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
    +---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | article |          0 | PRIMARY         |            1 | id          | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
    | article |          1 | idx_article_ccv |            1 | category_id | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
    | article |          1 | idx_article_ccv |            2 | views       | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
    +---------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    3 rows in set (0.00 sec)
    • 再次执行查询:可以看到,type变为了ref,Extra中的Using filesort也消失了,结果非常理想
    ysql> EXPLAIN SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
    +----+-------------+---------+------+-----------------+-----------------+---------+-------+------+-------------+
    | id | select_type | table   | type | possible_keys   | key             | key_len | ref   | rows | Extra       |
    +----+-------------+---------+------+-----------------+-----------------+---------+-------+------+-------------+
    |  1 | SIMPLE      | article | ref  | idx_article_ccv | idx_article_ccv | 4       | const |    2 | Using where |
    +----+-------------+---------+------+-----------------+-----------------+---------+-------+------+-------------+
    1 row in set (0.00 sec)
    • 为了不影响之后的测试,删除该表的 idx_article_ccv 索引
    mysql> DROP INDEX idx_article_ccv ON article;
    Query OK, 0 rows affected (0.05 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> SHOW INDEX FROM article;
    +---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | Table   | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
    +---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | article |          0 | PRIMARY  |            1 | id          | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
    +---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    1 row in set (0.01 sec)

    2、两表索引优化

    两表索引优化分析:主外键

    主要是两表连接查询:用小表驱动大表,大表索引优化越大,越快。

    创建表

    • 建表 SQL
    CREATE TABLE IF NOT EXISTS class(
    	id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    	card INT(10) UNSIGNED NOT NULL,
    	PRIMARY KEY(id)
    );
    
    CREATE TABLE IF NOT EXISTS book(
    	bookid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    	card INT(10) UNSIGNED NOT NULL,
    	PRIMARY KEY(bookid)
    );
    
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
    
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
    • class 表中的测试数据
    mysql> select * from class;
    +----+------+
    | id | card |
    +----+------+
    |  1 |   12 |
    |  2 |   13 |
    |  3 |   12 |
    |  4 |   17 |
    |  5 |   11 |
    |  6 |    3 |
    |  7 |    1 |
    |  8 |   16 |
    |  9 |   17 |
    | 10 |   16 |
    | 11 |    9 |
    | 12 |   17 |
    | 13 |   18 |
    | 14 |   16 |
    | 15 |    7 |
    | 16 |    8 |
    | 17 |   19 |
    | 18 |    9 |
    | 19 |    6 |
    | 20 |    5 |
    | 21 |    6 |
    +----+------+
    21 rows in set (0.00 sec)
    • book 表中的测试数据
    mysql> select * from book;
    +--------+------+
    | bookid | card |
    +--------+------+
    |      1 |   16 |
    |      2 |    1 |
    |      3 |   17 |
    |      4 |    3 |
    |      5 |   20 |
    |      6 |   12 |
    |      7 |   18 |
    |      8 |   13 |
    |      9 |   13 |
    |     10 |    4 |
    |     11 |    1 |
    |     12 |   13 |
    |     13 |   20 |
    |     14 |   20 |
    |     15 |    1 |
    |     16 |    2 |
    |     17 |    9 |
    |     18 |   16 |
    |     19 |   14 |
    |     20 |    2 |
    +--------+------+
    20 rows in set (0.00 sec)

    查询案例

    • 实现两表的连接,连接条件是 class.card = book.card
    mysql> SELECT * FROM class LEFT JOIN book ON class.card = book.card;
    +----+------+--------+------+
    | id | card | bookid | card |
    +----+------+--------+------+
    |  1 |   12 |      6 |   12 |
    |  2 |   13 |      8 |   13 |
    |  2 |   13 |      9 |   13 |
    |  2 |   13 |     12 |   13 |
    |  3 |   12 |      6 |   12 |
    |  4 |   17 |      3 |   17 |
    |  5 |   11 |   NULL | NULL |
    |  6 |    3 |      4 |    3 |
    |  7 |    1 |      2 |    1 |
    |  7 |    1 |     11 |    1 |
    |  7 |    1 |     15 |    1 |
    |  8 |   16 |      1 |   16 |
    |  8 |   16 |     18 |   16 |
    |  9 |   17 |      3 |   17 |
    | 10 |   16 |      1 |   16 |
    | 10 |   16 |     18 |   16 |
    | 11 |    9 |     17 |    9 |
    | 12 |   17 |      3 |   17 |
    | 13 |   18 |      7 |   18 |
    | 14 |   16 |      1 |   16 |
    | 14 |   16 |     18 |   16 |
    | 15 |    7 |   NULL | NULL |
    | 16 |    8 |   NULL | NULL |
    | 17 |   19 |   NULL | NULL |
    | 18 |    9 |     17 |    9 |
    | 19 |    6 |   NULL | NULL |
    | 20 |    5 |   NULL | NULL |
    | 21 |    6 |   NULL | NULL |
    +----+------+--------+------+
    28 rows in set (0.00 sec)
    • 使用 explain 分析 SQL 语句的性能,可以看到:驱动表是左表 class 表
    mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
    +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                                              |
    +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------------+
    |  1 | SIMPLE      | class | ALL  | NULL          | NULL | NULL    | NULL |   21 | NULL                                               |
    |  1 | SIMPLE      | book  | ALL  | NULL          | NULL | NULL    | NULL |   20 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------------+
    2 rows in set (0.00 sec)
    • 结论:
      • type 有 All ,rows 为表中数据总行数,说明 class 和 book 进行了全表检索
      • 即每次 class 表对 book 表进行左外连接时,都需要在 book 表中进行一次全表检索

    添加索引:在右表添加索引

    • 添加索引的 SQL 指令
    ALTER TABLE 'book' ADD INDEX Y ('card');
    • 在 book 的 card 字段上添加索引
    mysql> ALTER TABLE book ADD INDEX Y (card);
    Query OK, 0 rows affected (0.30 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> SHOW INDEX FROM book;
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | book  |          0 | PRIMARY  |            1 | bookid      | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
    | book  |          1 | Y        |            1 | card        | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    2 rows in set (0.00 sec)
    • 测试结果:可以看到第二行的type变为了ref,rows也变成了优化比较明显。
    mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
    +----+-------------+-------+------+---------------+------+---------+-----------------+------+-------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref             | rows | Extra       |
    +----+-------------+-------+------+---------------+------+---------+-----------------+------+-------------+
    |  1 | SIMPLE      | class | ALL  | NULL          | NULL | NULL    | NULL            |   21 | NULL        |
    |  1 | SIMPLE      | book  | ref  | Y             | Y    | 4       | db01.class.card |    1 | Using index |
    +----+-------------+-------+------+---------------+------+---------+-----------------+------+-------------+
    2 rows in set (0.00 sec)
    • 分析:
      • 这是由左连接特性决定的。LEFT JOIN条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引。
      • 左表连接右表,则需要拿着左表的数据去右表里面查,索引需要在右表中建立索引

    添加索引:在右表添加索引

    • 删除之前 book 表中的索引
    DROP INDEX Y ON book;
    • 在 class 表的 card 字段上建立索引
    ALTER TABLE class ADD INDEX X(card);
    • 再次执行左连接,凉凉~~~
    mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
    +----+-------------+-------+-------+---------------+------+---------+------+------+----------------------------------------------------+
    | id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                                              |
    +----+-------------+-------+-------+---------------+------+---------+------+------+----------------------------------------------------+
    |  1 | SIMPLE      | class | index | NULL          | X    | 4       | NULL |   21 | Using index                                        |
    |  1 | SIMPLE      | book  | ALL   | NULL          | NULL | NULL    | NULL |   20 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------------+-------+-------+---------------+------+---------+------+------+----------------------------------------------------+
    2 rows in set (0.00 sec)
    • 别怕,我们来执行右连接:可以看到第二行的type变为了ref,rows也变成了优化比较明显。
    mysql> EXPLAIN SELECT * FROM class RIGHT JOIN book ON class.card = book.card;
    +----+-------------+-------+------+---------------+------+---------+----------------+------+-------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref            | rows | Extra       |
    +----+-------------+-------+------+---------------+------+---------+----------------+------+-------------+
    |  1 | SIMPLE      | book  | ALL  | NULL          | NULL | NULL    | NULL           |   20 | NULL        |
    |  1 | SIMPLE      | class | ref  | X             | X    | 4       | db01.book.card |    1 | Using index |
    +----+-------------+-------+------+---------------+------+---------+----------------+------+-------------+
    2 rows in set (0.00 sec)
    • 分析:
    • 这是因为RIGHT JOIN条件用于确定如何从左表搜索行,右边一定都有,所以左边是我们的关键点,一定需要建立索引。
    • class RIGHT JOIN book :book 里面的数据一定存在于结果集中,我们需要拿着 book 表中的数据,去 class 表中搜索,所以索引需要建立在 class 表中
    • 为了不影响之后的测试,删除该表的 idx_article_ccv 索引
    mysql> DROP INDEX X ON class;
    Query OK, 0 rows affected (0.04 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> SHOW INDEX FROM class;
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | class |          0 | PRIMARY  |            1 | id          | A         |          21 |     NULL | NULL   |      | BTREE      |         |               |
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    1 row in set (0.00 sec)

    3、三表索引优化

    三表索引优化分析

    创建表

    • 建表 SQL
    CREATE TABLE IF NOT EXISTS phone(
    	phoneid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    	card INT(10) UNSIGNED NOT NULL,
    	PRIMARY KEY(phoneid)
    )ENGINE=INNODB;
    
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
    • phone 表中的测试数据
    mysql> select * from phone;
    +---------+------+
    | phoneid | card |
    +---------+------+
    |       1 |    7 |
    |       2 |    7 |
    |       3 |   13 |
    |       4 |    6 |
    |       5 |    8 |
    |       6 |    4 |
    |       7 |   16 |
    |       8 |    4 |
    |       9 |   15 |
    |      10 |    1 |
    |      11 |   20 |
    |      12 |   18 |
    |      13 |    9 |
    |      14 |    9 |
    |      15 |   20 |
    |      16 |   11 |
    |      17 |   15 |
    |      18 |    3 |
    |      19 |    8 |
    |      20 |   10 |
    +---------+------+
    20 rows in set (0.00 sec)

    查询案例

    • 实现三表的连接查询:
    mysql> SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card;
    +----+------+--------+------+---------+------+
    | id | card | bookid | card | phoneid | card |
    +----+------+--------+------+---------+------+
    |  2 |   13 |      8 |   13 |       3 |   13 |
    |  2 |   13 |      9 |   13 |       3 |   13 |
    |  2 |   13 |     12 |   13 |       3 |   13 |
    |  8 |   16 |      1 |   16 |       7 |   16 |
    | 10 |   16 |      1 |   16 |       7 |   16 |
    | 14 |   16 |      1 |   16 |       7 |   16 |
    |  8 |   16 |     18 |   16 |       7 |   16 |
    | 10 |   16 |     18 |   16 |       7 |   16 |
    | 14 |   16 |     18 |   16 |       7 |   16 |
    |  7 |    1 |      2 |    1 |      10 |    1 |
    |  7 |    1 |     11 |    1 |      10 |    1 |
    |  7 |    1 |     15 |    1 |      10 |    1 |
    | 13 |   18 |      7 |   18 |      12 |   18 |
    | 11 |    9 |     17 |    9 |      13 |    9 |
    | 18 |    9 |     17 |    9 |      13 |    9 |
    | 11 |    9 |     17 |    9 |      14 |    9 |
    | 18 |    9 |     17 |    9 |      14 |    9 |
    |  6 |    3 |      4 |    3 |      18 |    3 |
    |  4 |   17 |      3 |   17 |    NULL | NULL |
    |  9 |   17 |      3 |   17 |    NULL | NULL |
    | 12 |   17 |      3 |   17 |    NULL | NULL |
    |  1 |   12 |      6 |   12 |    NULL | NULL |
    |  3 |   12 |      6 |   12 |    NULL | NULL |
    |  5 |   11 |   NULL | NULL |    NULL | NULL |
    | 15 |    7 |   NULL | NULL |    NULL | NULL |
    | 16 |    8 |   NULL | NULL |    NULL | NULL |
    | 17 |   19 |   NULL | NULL |    NULL | NULL |
    | 19 |    6 |   NULL | NULL |    NULL | NULL |
    | 20 |    5 |   NULL | NULL |    NULL | NULL |
    | 21 |    6 |   NULL | NULL |    NULL | NULL |
    +----+------+--------+------+---------+------+
    30 rows in set (0.00 sec)
    • 使用 explain 分析 SQL 指令:
    mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card;
    +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                                              |
    +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------------+
    |  1 | SIMPLE      | class | ALL  | NULL          | NULL | NULL    | NULL |   21 | NULL                                               |
    |  1 | SIMPLE      | book  | ALL  | NULL          | NULL | NULL    | NULL |   20 | Using where; Using join buffer (Block Nested Loop) |
    |  1 | SIMPLE      | phone | ALL  | NULL          | NULL | NULL    | NULL |   20 | Using where; Using join buffer (Block Nested Loop) |
    +----+-------------+-------+------+---------------+------+---------+------+------+----------------------------------------------------+
    3 rows in set (0.00 sec)
    • 结论:
      • type 有All ,rows 为表数据总行数,说明 class、 book 和 phone 表都进行了全表检索
      • Extra 中 Using join buffer ,表明连接过程中使用了 join 缓冲区

    创建索引

    • 创建索引的 SQL 语句
    ALTER TABLE book ADD INDEX Y (card);
    ALTER TABLE phone ADD INDEX Z (card);
    • 进行 LEFT JOIN ,永远都在右表的字段上建立索引
    mysql> ALTER TABLE book ADD INDEX Y (card);
    Query OK, 0 rows affected (0.06 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> SHOW INDEX FROM book;
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | book  |          0 | PRIMARY  |            1 | bookid      | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
    | book  |          1 | Y        |            1 | card        | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    2 rows in set (0.00 sec)
    
    mysql> ALTER TABLE phone ADD INDEX Z (card);
    Query OK, 0 rows affected (0.05 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> SHOW INDEX FROM phone;
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | phone |          0 | PRIMARY  |            1 | phoneid     | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
    | phone |          1 | Z        |            1 | card        | A         |          20 |     NULL | NULL   |      | BTREE      |         |               |
    +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    2 rows in set (0.00 sec)
    • 执行查询:后2行的type都是ref,且总rows优化很好,效果不错。因此索引最好设置在需要经常查询的字段中。
    mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card=book.card LEFT JOIN phone ON book.card = phone.card;
    +----+-------------+-------+------+---------------+------+---------+-----------------+------+-------------+
    | id | select_type | table | type | possible_keys | key  | key_len | ref             | rows | Extra       |
    +----+-------------+-------+------+---------------+------+---------+-----------------+------+-------------+
    |  1 | SIMPLE      | class | ALL  | NULL          | NULL | NULL    | NULL            |   21 | NULL        |
    |  1 | SIMPLE      | book  | ref  | Y             | Y    | 4       | db01.class.card |    1 | Using index |
    |  1 | SIMPLE      | phone | ref  | Z             | Z    | 4       | db01.book.card  |    1 | Using index |
    +----+-------------+-------+------+---------------+------+---------+-----------------+------+-------------+
    3 rows in set (0.00 sec)

    Join 语句优化的结论

    将 left join 看作是两层嵌套 for 循环

    1. 尽可能减少Join语句中的NestedLoop的循环总次数;
    2. 永远用小结果集驱动大的结果集(在大结果集中建立索引,在小结果集中遍历全表);
    3. 优先优化NestedLoop的内层循环;
    4. 保证Join语句中被驱动表上Join条件字段已经被索引;
    5. 当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝惜JoinBuffer的设置;

    我的理解

    1. 使用小表驱动大表,这就相当于外层 for 循环的次数少,内层 for 循环的次数多。
    2. 然后我们在大表中建立了索引,这样内层 for 循环的效率明显提高
    3. 综上,使用小表驱动大表,在大表中建立了索引

    小表的每条都得遍历,用小表的id在大表中用索引查,所以快。

    转自此文章B站此视频

  • 相关阅读:
    kettle初探
    ECLIPSE 不能连接MYSQL 8 的现象
    easyreport 安装手记
    eclipse 下安装 lombok.jar
    mysql 8 安装及更改密码
    转:Python的这几个技巧,简直屌爆了
    转:通用缩写表
    检查webpack版本
    vue-cli脚手架项目按需引入elementUI
    webstorm “Unterminated statement”
  • 原文地址:https://www.cnblogs.com/friend-c/p/15395198.html
Copyright © 2011-2022 走看看