MySQL分组查询优化

4

我有三个表:categories、articles和article_events,其结构如下:

categories: id, name                        (100,000 rows)
articles: id, category_id                   (6000 rows)
article_events: id, article_id, status_id   (20,000 rows)

每行文章的最高article_events.id描述了每篇文章的当前状态。

我正在返回一个表格,显示每个类别中有多少文章处于最新事件状态_id为'1'。

到目前为止,我的代码是可以运行的,但是在我的表格大小下比较慢(需要10秒)。想知道是否有一种方法可以加速。据我所知,所有的表都有适当的索引。

SELECT c.id, 
       c.name, 
       SUM(CASE WHEN e.status_id = 1 THEN 1 ELSE 0 END) article_count
FROM categories c
LEFT JOIN articles a ON a.category_id = c.id
LEFT JOIN (
    SELECT article_id, MAX(id) event_id
    FROM article_events
    GROUP BY article_id
) most_recent ON most_recent.article_id = a.id
LEFT JOIN article_events e ON most_recent.event_id = e.id
GROUP BY c.id

基本上我需要两次加入事件表,因为只要请求status_id和MAX(id)一起,它就会返回它找到的第一个status_id,而不是与MAX(id)行相关联的那个。
有没有什么方法让它更好?还是说我必须用10秒钟来活着?谢谢!
编辑:
这是我查询的EXPLAIN:
ID | select_type | table          | type   | possible_keys | key         | key_len | ref                  | rows   | Extra 
---------------------------------------------------------------------------------------------------------------------------
1  | PRIMARY     | c              | index  | NULL          | PRIMARY     | 4       | NULL                 | 124044 | Using index; Using temporary; Using filesort
1  | PRIMARY     | a              | ref    | category_id   | category_id | 4       | c.id                 | 3      |
1  | PRIMARY     | <derived2>     | ALL    | NULL          | NULL        | NULL    | NULL                 | 6351   |
1  | PRIMARY     | e              | eq_ref | PRIMARY       | PRIMARY     | 4       | most_recent.event_id | 1      |
2  | DERIVED     | article_events | ALL    | NULL          | NULL        | NULL    | NULL                 | 19743  | Using temporary; Using filesort

3
请在此处发布您查询的EXPLAIN ...输出结果。 - vyegorov
3个回答

1

如果您可以使用JOINs消除子查询,通常会表现更好,因为派生表无法使用索引。以下是没有子查询的查询:

SELECT c.id, 
       c.name, 
       COUNT(a1.article_id) AS article_count
FROM categories c
LEFT JOIN articles a ON a.category_id = c.id
LEFT JOIN article_events ae1
  ON ae1.article_id = a.id
LEFT JOIN article_events ae2
  ON ae2.article_id = a.id
  AND ae2.id > a1.id
WHERE ae2.id IS NULL
GROUP BY c.id

您需要尝试使用索引并使用EXPLAIN进行测试,但这是我的猜测(我假设id字段是主键,并且您正在使用InnoDB):

categories: `name`
articles: `category_id`
article_events: (`article_id`, `id`)

谢谢。我以前见过这种获取最新行的方法,但读到对于大表而言速度较慢。显然不是这样的。查询运行时间为0.0058秒。 - Charles

0

我不喜欢使用索引 categories.id,因为这样会选择整个表。

尝试运行:

ANALYZE TABLE categories;
ANALYZE TABLE article_events;

然后重新运行查询。


0

没试过,但我认为这将为数据库节省一些工作:

SELECT ae.article_id AS ref_article_id, 
    MAX(ae.id) event_id, 
    ae.status_id,
    (select a.category_id from articles a where a.id = ref_article_id) AS cat_id,
    (select c.name from categories c where c.id = cat_id) AS cat_name
FROM article_events
GROUP BY ae.article_id

希望这有所帮助。

编辑:

顺便提一下...请记住,连接必须通过每一行进行,因此如果可能的话,您应该从小端开始选择并逐步向上工作。在这种情况下,查询必须运行通过100,000条记录,并加入每个记录,然后再次加入这些100,000条记录,以此类推,即使值为null,它仍然必须经过这些操作。

希望这些都有所帮助...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接