MySQL查询太慢,70k行12秒

时间:2014-06-26 21:57:49

标签: php mysql query-optimization

此查询在VPS上运行12秒以上。它加入了3个表。只有第一个" topcics"有大约70k行,其他大约是20行和" post_cc"大约1500。

SELECT topics.*, employee.username, accounts.ac_name, accounts.ac_mail
FROM topics
INNER JOIN employee ON employee.id_user = topics.id_owner 
INNER JOIN accounts ON accounts.id_account = topics.id_account 
WHERE topics.status  IN  ('1','3') AND ( topics.id_owner IN (12, 5) OR topics.id_post IN 
    (SELECT DISTINCT(id_post) FROM post_cc WHERE id_employee IN (12, 5) ) )
ORDER BY topics.creationdate DESC LIMIT 0,25

我已经尝试过(没有任何改进)删除子查询和第一个"员工"加入。如果我删除"帐户" join,查询在0.1秒内运行,但在分页期间需要所有表数据进行排序。

说明:

+----+--------------------+------------+-----------------+-----------------------+---------+---------+-----------------+-------+----------------------------------------------+
| id | select_type        | table      | type            | possible_keys         | key     | key_len | ref             | rows  | Extra                                        |
+----+--------------------+------------+-----------------+-----------------------+---------+---------+-----------------+-------+----------------------------------------------+
|  1 | PRIMARY            | topics     | ALL             | id_owner,id_account   | NULL    | NULL    | NULL            | 75069 | Using where; Using temporary; Using filesort |
|  1 | PRIMARY            | accounts   | ALL             | PRIMARY               | NULL    | NULL    | NULL            |     5 | Using where; Using join buffer               |
|  1 | PRIMARY            | employee   | eq_ref          | PRIMARY               | PRIMARY | 3       | topics.st_owner |     1 | Using where                                  |
|  2 | DEPENDENT SUBQUERY | post_cc    | unique_subquery | PRIMARY               | PRIMARY | 8       | func,const      |     1 | Using index; Using where                     |
+----+--------------------+------------+-----------------+-----------------------+---------+---------+-----------------+-------+----------------------------------------------+

我已将建议的密钥添加为索引,它将时间提高了2秒,但它仍然太慢。

缩短的表格:

topics
+--------------------+---------------------+------+-----+---------+----------------+
| Field              | Type                | Null | Key | Default | Extra          |
+--------------------+---------------------+------+-----+---------+----------------+
| id_post            | int(10) unsigned    | NO   | PRI | NULL    | auto_increment |
| id_account         | int(10) unsigned    | YES  | MUL | 0       |                |
| mail               | varchar(256)        | YES  | MUL | NULL    |                |
| from_name          | varchar(512)        | YES  |     | NULL    |                |
| title              | varchar(512)        | YES  |     | NULL    |                |
| content            | text                | YES  |     | NULL    |                |
| id_owner           | int(10) unsigned    | YES  | MUL | NULL    |                |
| creationdate       | datetime            | YES  |     | NULL    |                |
+--------------------+---------------------+------+-----+---------+----------------+

employee
+---------------------+-----------------------+------+-----+---------+----------------+
| Field               | Type                  | Null | Key | Default | Extra          |
+---------------------+-----------------------+------+-----+---------+----------------+
| id_employee         | mediumint(8) unsigned | NO   | PRI | NULL    | auto_increment |
| id_user             | mediumint(8) unsigned | NO   |     | NULL    |                |
| id_owner            | tinyint(1)            | YES  |     | 0       |                |
| active              | tinyint(1)            | YES  |     | 1       |                |
| username            | varchar(64)           | YES  |     | NULL    |                |
| email               | varchar(128)          | YES  |     | NULL    |                |
+---------------------+-----------------------+------+-----+---------+----------------+

accounts
+----------------------------+---------------------+------+-----+---------+----------------+
| Field                      | Type                | Null | Key | Default | Extra          |
+----------------------------+---------------------+------+-----+---------+----------------+
| id_account                 | int(10) unsigned    | NO   | PRI | NULL    | auto_increment |
| ac_mail                    | int(10) unsigned    | YES  | UNI | NULL    |                |
| ac_name                    | varchar(512)        | YES  |     | NULL    |                |
| last_sync_time             | datetime            | YES  |     | NULL    |                |
+----------------------------+---------------------+------+-----+---------+----------------+

post_cc
+------------------------+---------------------+------+-----+---------+-------+
| Field                  | Type                | Null | Key | Default | Extra |
+------------------------+---------------------+------+-----+---------+-------+
| id_post                | int(10) unsigned    | NO   | PRI | NULL    |       |
| id_employee            | int(10) unsigned    | NO   | PRI | NULL    |       |
| notifications          | tinyint(3) unsigned | YES  |     | 1       |       |
+------------------------+---------------------+------+-----+---------+-------+

3 个答案:

答案 0 :(得分:2)

一个可能怀疑的是依赖性提示。

MySQL正在处理外部查询返回的每一行的子查询(它已经被其他谓词过滤掉了。

要提高性能,请考虑将其重写为JOIN操作或EXISTS谓词。

要用JOIN操作替换它,因为谓词中的OR,所以需要是OUTER JOIN(而不是INNER JOIN)。

作为一种方法的例子:

SELECT topics.*
     , employee.username
     , accounts.ac_name
     , accounts.ac_mail
  FROM topics
  JOIN employee ON employee.id_user = topics.id_owner 
  JOIN accounts ON accounts.id_account = topics.id_account
  LEFT
  JOIN ( SELECT DISTINCT q.id_post
           FROM post_cc q 
          WHERE q.id_employee IN (12, 5) 
       ) p
    ON p.id_post = topics.id_post   
 WHERE topics.status IN ('1','3') 
   AND ( topics.id_owner IN (12, 5) 
       OR p.id_post IS NOT NULL
       )
 ORDER BY topics.creationdate DESC LIMIT 0,25

我建议你对它运行一个EXPLAIN,看看它是如何运作的。


另一种选择是考虑EXISTS谓词。在偶然的情况下,我们可以让它表现得更好,但往往不是。

SELECT topics.*
     , employee.username
     , accounts.ac_name
     , accounts.ac_mail
  FROM topics
  JOIN employee ON employee.id_user = topics.id_owner 
  JOIN accounts ON accounts.id_account = topics.id_account
 WHERE topics.status IN ('1','3') 
   AND ( topics.id_owner IN (12, 5) 
       OR EXISTS ( SELECT 1 
                     FROM post_cc q
                    WHERE q.id_employee IN (12, 5)
                      AND q.id_post = topics.id_post
                 )
       )
 ORDER BY topics.creationdate DESC LIMIT 0,25

对于性能,这几乎需要EXISTS子句中子查询的合适覆盖索引,例如:

ON post_cc (id_post, id_employee)

你可以尝试运行一个EXPLAIN,看看它的表现如何。


我们发现MySQL并没有在topics表上使用索引。

我们可能会让MySQL避免昂贵的"使用filesort"如果我们的索引的前导列为creationdate

部分问题可能是谓词中的 OR 。我们可能会尝试将该查询重写为两个单独的查询,并将它们与UNION ALL集合操作相结合。但是,如果我们这样做,我们真的希望看到使用topic上的索引(我们可能通过招致两次扫描70,000行来提高性能。

SELECT topics.*
     , employee.username
     , accounts.ac_name
     , accounts.ac_mail
  FROM topics
  JOIN employee ON employee.id_user = topics.id_owner 
  JOIN accounts ON accounts.id_account = topics.id_account
 WHERE topics.status IN ('1','3')
   AND topics.id_owner IN (12, 5)

 UNION ALL

SELECT topics.*
     , employee.username
     , accounts.ac_name
     , accounts.ac_mail
  FROM topics
  JOIN employee ON employee.id_user = topics.id_owner 
  JOIN accounts ON accounts.id_account = topics.id_account
  JOIN ( SELECT DISTINCT q.id_post
           FROM post_cc q 
          WHERE q.id_employee IN (12, 5) 
       ) p
    ON p.id_post = topics.id_post  
 WHERE topics.status IN ('1','3')
   AND ( topics.id_owner NOT IN (12, 5) OR topics.id_owner IS NULL )

 ORDER BY 8 DESC LIMIT 0,25

通过查询该表单,我们更有可能让MySQL在主题表上使用合适的索引,

... ON topics (id_owner, status)

... ON topics (id_post, status, id_owner)

答案 1 :(得分:0)

为什么不在post_cc表中使用左连接,然后使用条件?

像这样。

SELECT topics.*, 
employee.username, 
accounts.ac_name, 
accounts.ac_mail
FROM topics
INNER JOIN employee ON employee.id_user = topics.id_owner 
INNER JOIN accounts ON accounts.id_account = topics.id_account 
LEFT JOIN post_cc ON id_employee IN (12, 5)
WHERE topics.status  IN  ('1','3') 
AND ( topics.id_owner IN (12, 5) OR topics.id_post IN (post_cc.post_id)
ORDER BY topics.creationdate DESC LIMIT 0,25;

答案 2 :(得分:0)

罪魁祸首:

  

75069(行)|用在哪里;使用临时;使用filesort

这就是我们需要摆脱的东西。

可能的解决方案:在topics.creationdate上添加索引。

作为旁注,该查询还具有id_postst_ownerstatus的条件,因此是topics(creationdate, id_post, st_owner, status)上的综合索引(或最后一个的排列)三列 - 使用您的数据集进行测试)可能会有所帮助。但是,您的查询似乎无论如何都会拉动您的大部分表,因此我希望简单的索引就足够了。