加速大型MySQL查询



下面的查询在"发送数据"阶段挂起了很长时间。这是一个很大的查询,但我希望得到一些帮助,我的索引,并可能了解更多关于MySQL实际上是如何选择哪个索引将使用。

下面是查询和描述语句的输出。

mysql> DESCRIBE SELECT e.employee_number, s.current_status_start_date, e.company_code, e.location_code, s.last_suffix_first_mi, s.job_title, SUBSTRING(e.job_code,1,1) tt_jobCode,
->                      SUM(e.current_amount) tt_grossWages,
->                      IFNULL((SUM(e.current_amount) - IF(tt1.tt_reduction = '','0',tt1.tt_reduction)),SUM(e.current_amount)) tt_taxableWages,
->                      t.new_code, STR_TO_DATE(s.last_hire_date, '%Y-%m-%d') tt_hireDate,
->                      IF(s.current_status_code = 'T',STR_TO_DATE(s.current_status_start_date, '%Y-%m-%d'),'') tt_terminationDate,
->                      IFNULL(tt_totalHours,'0') tt_totalHours
->               FROM check_earnings e
->               LEFT JOIN (
->                          SELECT * FROM summary
->                          GROUP BY employee_no
->                          ORDER BY current_status_start_date DESC
->                         ) s
->               ON e.employee_number = s.employee_no
->               LEFT JOIN (
->                          SELECT employee_no, SUM(current_amount__employee) tt_reduction
->                          FROM check_deductions
->                          WHERE STR_TO_DATE(pay_date, '%Y-%m-%d') >= STR_TO_DATE('2012-06-01', '%Y-%m-%d')
->                          AND STR_TO_DATE(pay_date, '%Y-%m-%d') <= STR_TO_DATE('2013-06-01', '%Y-%m-%d')
->                          AND (
->                               deduction_code IN ('DECMP','FSAM','FSAC','DCMAK','DCMAT','401KD')
->                               OR deduction_code LIKE 'IM%'
->                               OR deduction_code LIKE 'ID%'
->                               OR deduction_code LIKE 'IV%'
->                               )
->                          GROUP BY employee_no
->                          ORDER BY employee_no ASC
->                          ) tt1
->               ON e.employee_number = tt1.employee_no
->               LEFT JOIN translation t
->               ON e.location_code = t.old_code
->               LEFT JOIN (
->                          SELECT employee_number, SUM(current_hours) tt_totalHours
->                          FROM check_earnings
->                          WHERE STR_TO_DATE(pay_date, '%Y-%m-%d') >= STR_TO_DATE('2012-06-01', '%Y-%m-%d')
->                          AND STR_TO_DATE(pay_date, '%Y-%m-%d') <= STR_TO_DATE('2013-06-01', '%Y-%m-%d')
->                          AND earnings_code IN ('REG1','REG2','REG3','REG4')
->                          GROUP BY employee_number
->                         ) tt2
->               ON e.employee_number = tt2.employee_number
->               WHERE STR_TO_DATE(e.pay_date, '%Y-%m-%d') >= STR_TO_DATE('2012-06-01', '%Y-%m-%d')
->               AND STR_TO_DATE(e.pay_date, '%Y-%m-%d') <= STR_TO_DATE('2013-06-01', '%Y-%m-%d')
->               AND SUBSTRING(e.job_code,1,1) != 'E'
->               AND e.location_code != '639'
->               AND t.field = 'location_state'
->               GROUP BY e.employee_number
->               ORDER BY s.current_status_start_date DESC, e.location_code ASC, s.last_suffix_first_mi ASC;
+----+-------------+------------------+-------+----------------+-----------------+---------+----------------------------+---------+----------------------------------------------+
| id | select_type | table            | type  | possible_keys  | key             | key_len | ref                        | rows    | Extra                                        |
+----+-------------+------------------+-------+----------------+-----------------+---------+----------------------------+---------+----------------------------------------------+
|  1 | PRIMARY     | e                | ALL   | location_code  | NULL            | NULL    | NULL                       | 3498603 | Using where; Using temporary; Using filesort |
|  1 | PRIMARY     | t                | ref   | field,old_code | old_code        | 303     | historical.e.location_code |       1 | Using where                                  |
|  1 | PRIMARY     | <derived2>       | ALL   | NULL           | NULL            | NULL    | NULL                       |   16741 |                                              |
|  1 | PRIMARY     | <derived3>       | ALL   | NULL           | NULL            | NULL    | NULL                       |    2530 |                                              |
|  1 | PRIMARY     | <derived4>       | ALL   | NULL           | NULL            | NULL    | NULL                       |    2919 |                                              |
|  4 | DERIVED     | check_earnings   | index | NULL           | employee_number | 303     | NULL                       | 3498603 | Using where                                  |
|  3 | DERIVED     | check_deductions | index | deduction_code | employee_no     | 303     | NULL                       | 6387048 | Using where                                  |
|  2 | DERIVED     | summary          | index | NULL           | employee_no     | 303     | NULL                       |   17608 | Using temporary; Using filesort              |
+----+-------------+------------------+-------+----------------+-----------------+---------+----------------------------+---------+----------------------------------------------+
8 rows in set, 65535 warnings (32.77 sec)

编辑:在使用了一些索引之后,它现在花费了大部分时间在"复制到tmp表"状态。

您无法避免在该查询中使用临时表。一个原因是您按不同的列分组,而不是按列排序。

另一个原因是使用了派生表(FROM/JOIN子句中的子查询)。

加快速度的一种方法是创建汇总表来存储这些子查询的结果,这样你就不必在每次查询时都做这些了。

您还通过搜索STR_TO_DATE()和SUBSTR()等函数的结果来强制表扫描。


你的评论:

我可以用一个优化得很差的查询对一个小得多的表运行72小时的SQL查询。

注意,例如在DESCRIBE的输出中,它为连接中涉及的几个表显示了"ALL"。这意味着它必须对所有行(如'rows'列所示)进行表扫描。

经验法则:解决连接需要多少行比较?用相同的'id'连接所有表的'行'。

+----+-------------+------------------+-------+---------+
| id | select_type | table            | type  | rows    |
+----+-------------+------------------+-------+---------+
|  1 | PRIMARY     | e                | ALL   | 3498603 |
|  1 | PRIMARY     | t                | ref   |       1 |
|  1 | PRIMARY     | <derived2>       | ALL   |   16741 |
|  1 | PRIMARY     | <derived3>       | ALL   |    2530 |
|  1 | PRIMARY     | <derived4>       | ALL   |    2919 |

所以它可能会计算连接条件432,544,383,105,752,610次(假设这些数字是近似值,所以它可能没有那么糟糕)。这实际上是一个奇迹,它只需要5个小时!

你需要做的是使用索引来帮助查询减少需要检查的行数。

例如,为什么要使用STR_TO_DATE(),因为你正在解析的日期是MySQL的原生日期格式?为什么不将这些列存储为DATE数据类型呢?然后搜索可以使用索引。

你不需要"玩弄索引"。这并不是说索引是一个谜,或者有随机的效果。

最新更新