mysql - 在对索引列进行排序时,如何避免在具有大表的 MySQL 中选择糟糕的执行计划?

标签 mysql sql

我在 MySQL 5.5 上有三个 InnoDB 表:

user_company_assignment (1.7 million rows)
 - id (int)
 - company_id (int) FKEY (company.id)
 - user_id (int) FKEY (user.id)

company (200k rows)
 - id (int)
 - name (varchar(255)) - Indexed

user (2 million rows)
 - id (int)
 - name (varchar(255)) - Indexed

连接所有表并按索引 varchar 字段之一排序具有良好的执行计划并且很快(~10 毫秒):

mysql> explain select user_company_assignment.id, company.name
  from user_company_assignment
  inner join company 
    on user_company_assignment.company_id = company.id
  inner join user
    on user_company_assignment.user_id = user.id
  order by company.name asc limit 5;
+----+-------------+------------+--------+-----------------------------+-------------+---------+------------------------------------------+------+-------------+
| id | select_type | table      | type   | possible_keys               | key         | key_len | ref                                      | rows | Extra       |
+----+-------------+------------+--------+-----------------------------+-------------+---------+------------------------------------------+------+-------------+
|  1 | SIMPLE      | company     | index  | PRIMARY                     | company_name  | 767     | NULL                                     |    1 | Using index |
|  1 | SIMPLE      | user_company_assignment  | ref    | fk_company_id,fk_user_id | fk_company_id | 4       | company.id              |    4 |             |
|  1 | SIMPLE      | user | eq_ref | PRIMARY                     | PRIMARY     | 4       | user_company_assignment.user_id |    1 | Using index |
+----+-------------+------------+--------+-----------------------------+-------------+---------+------------------------------------------+------+-------------+
3 rows in set (0.09 sec)

但是当我连接两个表并按它们的每个索引名称排序时,它会选择一个非常慢的执行计划并且查询需要一分多钟才能完成:

mysql> explain select user_company_assignment.id, company.name
  from user_company_assignment
  inner join company 
    on user_company_assignment.company_id = company.id
  inner join user 
    on user_company_assignment.user_id = user.id
  order by company.name asc, user.name asc limit 5;
+----+-------------+------------+--------+-----------------------------+-------------+---------+------------------------------------------+--------+----------------------------------------------+
| id | select_type | table      | type   | possible_keys               | key         | key_len | ref                                      | rows   | Extra                                        |
+----+-------------+------------+--------+-----------------------------+-------------+---------+------------------------------------------+--------+----------------------------------------------+
|  1 | SIMPLE      | company     | index  | PRIMARY                     | company_name  | 767     | NULL                                     | 180792 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | user_company_assignment  | ref    | fk_company_id,fk_user_id | fk_company_id | 4       | company.id              |      4 |                                              |
|  1 | SIMPLE      | user | eq_ref | PRIMARY                     | PRIMARY     | 4       | user_company_assignment.user_id |      1 |                                              |
+----+-------------+------------+--------+-----------------------------+-------------+---------+------------------------------------------+--------+----------------------------------------------+
3 rows in set (0.11 sec)

有没有办法获得将使用这两个表列上的索引的执行计划?我不希望它像仅按一列排序时那样快,但我觉得它应该只慢一点。

最佳答案

你可以用迂回的方式做你想做的事。

不幸的是,在 InnoDB 中无法创建包含来自单独表的列的键。因此,如果您按不同表中的列排序,然后使用 ORDER BY ... LIMIT 将它们切断,则 MySQL 将不得不选择每个匹配的行。这样做之后,它可以对磁盘上的大结果集进行排序,最后选择您想要的前几名。

解决方案是通过将匹配结果集限制为可管理的内容来减少 MySQL 必须操作的行数。

因为您知道最多总共有 5 个结果,所以您可以选择前 5 个公司 ID:

SELECT company.id
  FROM company 
  ORDER BY company.name ASC
  LIMIT 5

然后仅使用这些进行 JOIN,最后按公司和用户名排序:

SELECT user_company_assignment.id, top_companies.name
  FROM user_company_assignment
  INNER JOIN (
    SELECT company.name, company.id
      FROM company 
      ORDER BY company.name ASC
      LIMIT 5
    ) top_companies
    ON user_company_assignment.id = top_companies.id
  INNER JOIN user
    ON user_company_assignment.user_id = user.id
  ORDER BY top_companies.name ASC, user.name ASC
  LIMIT 5

Here's a SQL fiddle demonstrating the query running .我投入了少量数据进行测试。

这将仍然拉入相当数量的行进行文件排序,因为您将加入 5 家公司的所有员工,但它应该将您的查询的运行时间缩短几个数量级。

关于mysql - 在对索引列进行排序时,如何避免在具有大表的 MySQL 中选择糟糕的执行计划?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27971955/

相关文章:

php - 如何使用 PHP/SQL 构建树形 View ?

sql - 如何与 "row_number() over (partition by [Col] order by [Col])"相反

java - JDBC 准备语句 UTF-8

php - 如何使用 CodeIgniter 将图像保存在文件夹中?

php - Mysql 日期更新不起作用

sql - PL/SQL oracle 函数与 ora-01744 INTO

mysql - 如何修复 where 子句

MySQL 查询存储在一个表中以更新另一表中的值

php - Codeigniter MYSQL 连接作为单个记录返回?

mysql - 我们不能使用 getString() 从 mysql 数据库中获取日期字段类型吗