【问题标题】:MySQL. WordPress. Slow query when using IN statementsmysql。 WordPress。使用 IN 语句时查询慢
【发布时间】:2012-02-12 04:05:24
【问题描述】:

我正在尝试找出一种更好的方法来编写由 WordPress 的 WP_Query 类生成的以下查询。现在它非常很慢。

SELECT SQL_CALC_FOUND_ROWS wp_posts.*
FROM wp_posts
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)
INNER JOIN wp_postmeta AS mt2 ON (wp_posts.ID = mt2.post_id)
INNER JOIN wp_postmeta AS mt3 ON (wp_posts.ID = mt3.post_id)
INNER JOIN wp_postmeta AS mt4 ON (wp_posts.ID = mt4.post_id)
INNER JOIN wp_postmeta AS mt5 ON (wp_posts.ID = mt5.post_id)
INNER JOIN wp_postmeta AS mt6 ON (wp_posts.ID = mt6.post_id)
INNER JOIN wp_postmeta AS mt7 ON (wp_posts.ID = mt7.post_id)
INNER JOIN wp_postmeta AS mt8 ON (wp_posts.ID = mt8.post_id)

WHERE 1=1 AND wp_posts.post_type = 'gemstone'
AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'future' OR wp_posts.post_status = 'draft' OR wp_posts.post_status = 'pending' OR wp_posts.post_status = 'private')
AND (wp_postmeta.meta_key = 'gemstone_active_price'
    AND (mt1.meta_key = 'gemstone_status' AND CAST(mt1.meta_value AS CHAR) = 'Available')
    AND (mt2.meta_key = 'gemstone_length' AND CAST(mt2.meta_value AS DECIMAL(10,2)) BETWEEN '0' AND '9')
    AND (mt3.meta_key = 'gemstone_width' AND CAST(mt3.meta_value AS DECIMAL(10,2)) BETWEEN '0' AND '9')
    AND (mt4.meta_key = 'gemstone_depth' AND CAST(mt4.meta_value AS DECIMAL(10,2)) BETWEEN '0' AND '7')
    AND (mt5.meta_key = 'gemstone_color' AND CAST(mt5.meta_value AS CHAR) IN ('L','K','J','I','H','G','F','E','D'))
    AND (mt6.meta_key = 'gemstone_clarity' AND CAST(mt6.meta_value AS CHAR) IN ('I3','I2','I1','SI2','SI1','VS2','VVS2','VVS1','IF','FL'))
    AND (mt7.meta_key = 'gemstone_weight' AND CAST(mt7.meta_value AS DECIMAL(10,2)) BETWEEN '0.67' AND '1.85')
    AND (mt8.meta_key = 'gemstone_active_price' AND CAST(mt8.meta_value AS DECIMAL(10,2)) BETWEEN '960' AND '2300')
)

GROUP BY wp_posts.ID
ORDER BY wp_postmeta.meta_value+0 ASC
LIMIT 0, 20

我知道它看起来很乱,但是当我在 WHERE 子句(上面的 mt5 和 mt6)中没有 2 个 IN 语句时,整个事情执行得非常快。问题是,我不太了解 SQL,无法找出另一种方法来编写避免使用 IN 语句的查询。有什么想法吗?

更新: 这是此查询的EXPLAIN 输出,以防它对任何人有所帮助。如果有人有任何其他想法,我对任何事情都持开放态度。这让我完全被难住了。

id  select_type     table           type    possible_keys               key         key_len     ref                         rows    Extra
1   SIMPLE          wp_postmeta     ref     post_id,meta_key            meta_key    768         const                       2       Using where; Using temporary; Using filesort
1   SIMPLE          mt1             ref     post_id,meta_key            post_id     8           db.wp_postmeta.post_id      2       Using where
1   SIMPLE          mt2             ref     post_id,meta_key            post_id     8           db.mt1.post_id              2       Using where
1   SIMPLE          mt3             ref     post_id,meta_key            post_id     8           db.wp_postmeta.post_id      2       Using where
1   SIMPLE          mt4             ref     post_id,meta_key            post_id     8           db.mt2.post_id              2       Using where
1   SIMPLE          mt5             ref     post_id,meta_key            post_id     8           db.wp_postmeta.post_id      2       Using where
1   SIMPLE          mt6             ref     post_id,meta_key            post_id     8           db.wp_postmeta.post_id      2       Using where
1   SIMPLE          mt7             ref     post_id,meta_key            post_id     8           db.mt3.post_id              2       Using where
1   SIMPLE          mt8             ref     post_id,meta_key            post_id     8           db.wp_postmeta.post_id      2       Using where
1   SIMPLE          wp_posts        eq_ref  PRIMARY,type_status_date    PRIMARY     8           db.wp_postmeta.post_id      1       Using where

更新 2: 经过一些更多的实验,我意识到不仅仅是IN() 语句会减慢这个查询的速度。似乎任何一个以上的 IN() 与超过 3 个 BETWEEN...AND... 语句的组合都会对性能产生巨大影响。

例如,如果我删除最后 2 个 AND 子句,查询将在大约 0.04 秒内执行(而使用它们时需要 4.9 秒),或者如果我删除带有 @987654335 的 2 个 AND 子句,它将在 0.04 秒内执行@ 声明。这让我认为 2 查询解决方案可能是最好的,但我不知道如何通过 WordPress WP_Query API 来实现它,如果我这样做了,我想知道这是否比只做一个查询更快然后通过 PHP 过滤结果。

我讨厌使用 PHP 进行过滤的想法,因为我在几个地方读到过,过滤应该留给数据库,因为这是数据库擅长的。顺便说一句,如果有什么不同的话,我正在我的localhost WAMP 服务器上的 WordPress 3.3.1 安装上运行这些查询,该服务器具有足够的处理能力(Intel i7、12 GB RAM 等)。

更新 3: 我正在考虑放弃并从查询中删除所有 IN() 子句并通过 PHP 过滤这些子句,但这有一些严重的缺点。除了效率低下和代码异味之外,它还不允许我正确控制分页。当数据库中的所有内容都被过滤后,我可以简单地使用LIMIT 子句来处理分页。当我使用 PHP 过滤时,我不知道对于任何给定的偏移量会返回多少结果。所以,所有的过滤确实需要在数据库中完成,问题是如何。有没有人对我有任何额外的建议?任何其他信息对任何人都有帮助吗?

更新 4: 在我寻找解决方案的过程中,我将它作为一个问题发布在 WordPress 核心 trac 系统 (http://core.trac.wordpress.org/ticket/20134) 中。那里的一位开发人员建议我尝试对我在元查询中使用IN 的任何内容使用分类法而不是元数据。我接受了这个建议,我看到了性能提升,但不幸的是,这还远远不够。旧查询需要 4 多秒才能运行,而使用分类法则需要 1 多秒。但是,我意识到我实际上需要 4 个 IN 类型子句(不是原来的 2 个)。使用 2 个额外的分类条款,查询需要 18 秒以上的时间来执行。所以,我回到第一方。我的一个想法(这可能是妄想)是这可能运行得如此缓慢,因为我只有很少的帖子符合标准。出于测试目的,我在数据库中只有 3 个帖子类型为 'gemstone'。会不会有什么关系?

如果有人感兴趣,我的新 SQL 如下所示:

SELECT SQL_CALC_FOUND_ROWS wp_posts.*
FROM wp_posts
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
INNER JOIN wp_term_relationships AS tt1 ON (wp_posts.ID = tt1.object_id)
INNER JOIN wp_term_relationships AS tt2 ON (wp_posts.ID = tt2.object_id)
INNER JOIN wp_term_relationships AS tt3 ON (wp_posts.ID = tt3.object_id)    
INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)
INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)
INNER JOIN wp_postmeta AS mt2 ON (wp_posts.ID = mt2.post_id)
INNER JOIN wp_postmeta AS mt3 ON (wp_posts.ID = mt3.post_id)
INNER JOIN wp_postmeta AS mt4 ON (wp_posts.ID = mt4.post_id)
INNER JOIN wp_postmeta AS mt5 ON (wp_posts.ID = mt5.post_id)
INNER JOIN wp_postmeta AS mt6 ON (wp_posts.ID = mt6.post_id)

WHERE 1=1
AND ( wp_term_relationships.term_taxonomy_id IN (71,72,73,74)
    AND tt1.term_taxonomy_id IN (89,90,91,92,93,95,96,97)
    AND tt2.term_taxonomy_id IN (56,50,104,53)
    AND tt3.term_taxonomy_id IN (59,60,62)
)
AND wp_posts.post_type = 'gemstone'
AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'future' OR wp_posts.post_status = 'draft' OR wp_posts.post_status = 'pending' OR wp_posts.post_status = 'private')
AND (wp_postmeta.meta_key = 'gemstone_weight'
    AND (mt1.meta_key = 'gemstone_status' AND CAST(mt1.meta_value AS CHAR) = 'Available')
    AND (mt2.meta_key = 'gemstone_length' AND CAST(mt2.meta_value AS DECIMAL(8,2)) BETWEEN '0' AND '9')
    AND (mt3.meta_key = 'gemstone_width' AND CAST(mt3.meta_value AS DECIMAL(8,2)) BETWEEN '0' AND '9' )
    AND (mt4.meta_key = 'gemstone_depth' AND CAST(mt4.meta_value AS DECIMAL(8,2)) BETWEEN '0' AND '7')
    AND (mt5.meta_key = 'gemstone_weight' AND CAST(mt5.meta_value AS DECIMAL(8,2)) BETWEEN '0.81' AND '1.81')
    AND (mt6.meta_key = 'gemstone_active_price' AND CAST(mt6.meta_value AS DECIMAL(8,2)) BETWEEN '1083.9' AND '2078.26')
)

GROUP BY wp_posts.ID
ORDER BY wp_postmeta.meta_value+0 ASC
LIMIT 0, 20

新的EXPLAIN输出如下:

id  select_type     table                   type    possible_keys               key         key_len     ref                     rows    Extra
1   SIMPLE          wp_postmeta             ref     post_id,meta_key            meta_key    768         const                   3       Using where; Using temporary; Using filesort
1   SIMPLE          tt3                     ref     PRIMARY,term_taxonomy_id    PRIMARY     8           db.wp_postmeta.post_id  1       Using where; Using index
1   SIMPLE          tt2                     ref     PRIMARY,term_taxonomy_id    PRIMARY     8           db.wp_postmeta.post_id  1       Using where; Using index
1   SIMPLE          wp_term_relationships   ref     PRIMARY,term_taxonomy_id    PRIMARY     8           db.tt2.object_id        1       Using where; Using index
1   SIMPLE          wp_posts                eq_ref  PRIMARY,type_status_date    PRIMARY     8           db.wp_postmeta.post_id  1       Using where
1   SIMPLE          tt1                     ref     PRIMARY,term_taxonomy_id    PRIMARY     8           db.wp_posts.ID          1       Using where; Using index
1   SIMPLE          mt5                     ref     post_id,meta_key            post_id     8           db.wp_posts.ID          2       Using where
1   SIMPLE          mt6                     ref     post_id,meta_key            post_id     8           db.wp_posts.ID          2       Using where
1   SIMPLE          mt1                     ref     post_id,meta_key            post_id     8           db.mt5.post_id          2       Using where
1   SIMPLE          mt2                     ref     post_id,meta_key            post_id     8           db.mt1.post_id          2       Using where
1   SIMPLE          mt3                     ref     post_id,meta_key            post_id     8           db.tt2.object_id        2       Using where
1   SIMPLE          mt4                     ref     post_id,meta_key            post_id     8           db.tt3.object_id        2       Using where

更新 5: 由于有评论,我最近在optimizing this query 上进行了另一次尝试,但我得出的结论是,SQL 几乎必须按原样设置。然而,在测试一些替代方案时,我发现奇怪的是查询现在运行得更快了。我还没有更新我的 MySQL 服务器,所以我能理解的唯一原因是 WordPress 以某种方式更新了他们的数据库结构以提高性能。更新 4 中显示的完全相同的查询现在大约需要 2.4 秒。我认为仍然太长了(所以我仍在使用 STRAIGHT_JOIN,如下面的答案所示),但我对改进感到惊讶,这让我想知道是否有不同的解决方案可以进一步优化。这是新的 EXPLAIN 输出。它看起来和我几乎一模一样,但我真的不知道如何解释它。

+-----+--------------+------------------------+---------+---------------------------+-----------+----------+-------------------------------------+-------+----------------------------------------------+
| id  | select_type  |         table          |  type   |      possible_keys        |   key     | key_len  |                ref                  | rows  |                    Extra                     |
+-----+--------------+------------------------+---------+---------------------------+-----------+----------+-------------------------------------+-------+----------------------------------------------+
|  1  | SIMPLE       | wp_postmeta            | ref     | post_id,meta_key          | meta_key  |     768  | const                               |    5  | Using where; Using temporary; Using filesort |
|  1  | SIMPLE       | wp_term_relationships  | ref     | PRIMARY,term_taxonomy_id  | PRIMARY   |       8  | db.wp_postmeta.post_id              |    1  | Using where; Using index                     |
|  1  | SIMPLE       | tt2                    | ref     | PRIMARY,term_taxonomy_id  | PRIMARY   |       8  | db.wp_term_relationships.object_id  |    1  | Using where; Using index                     |
|  1  | SIMPLE       | tt3                    | ref     | PRIMARY,term_taxonomy_id  | PRIMARY   |       8  | db.wp_term_relationships.object_id  |    1  | Using where; Using index                     |
|  1  | SIMPLE       | wp_posts               | eq_ref  | PRIMARY,type_status_date  | PRIMARY   |       8  | db.wp_postmeta.post_id              |    1  | Using where                                  |
|  1  | SIMPLE       | tt1                    | ref     | PRIMARY,term_taxonomy_id  | PRIMARY   |       8  | db.wp_posts.ID                      |    1  | Using where; Using index                     |
|  1  | SIMPLE       | mt3                    | ref     | post_id,meta_key          | post_id   |       8  | db.tt2.object_id                    |    3  | Using where                                  |
|  1  | SIMPLE       | mt4                    | ref     | post_id,meta_key          | post_id   |       8  | db.tt3.object_id                    |    3  | Using where                                  |
|  1  | SIMPLE       | mt5                    | ref     | post_id,meta_key          | post_id   |       8  | db.wp_posts.ID                      |    3  | Using where                                  |
|  1  | SIMPLE       | mt6                    | ref     | post_id,meta_key          | post_id   |       8  | db.wp_posts.ID                      |    3  | Using where                                  |
|  1  | SIMPLE       | mt1                    | ref     | post_id,meta_key          | post_id   |       8  | db.mt5.post_id                      |    3  | Using where                                  |
|  1  | SIMPLE       | mt2                    | ref     | post_id,meta_key          | post_id   |       8  | db.mt3.post_id                      |    3  | Using where                                  |
+-----+--------------+------------------------+---------+---------------------------+-----------+----------+-------------------------------------+-------+----------------------------------------------+

【问题讨论】:

  • 作为更新。当我在 phpMyAdmin 中打开分析并运行此查询时,“统计”占已用时间的 99%。有谁知道在这种情况下“统计”是什么意思?
  • 尝试删除额外的 INNER JOIN,正如我在这里解释的那样:stackoverflow.com/a/15398104/212076

标签: mysql performance wordpress


【解决方案1】:

我现在偶然发现的“解决方案”非常难看,但由于某些莫名其妙的原因,它确实有效。添加 STRAIGHT_JOIN 优化器提示将执行时间从 18+ 秒减少到大约 0.0022 秒。基于常识和这个问题 (When to use STRAIGHT_JOIN with MySQL),这个解决方案似乎是个坏主意,但这是我尝试过的唯一有效的方法。所以,至少现在,我会坚持下去。如果有人对我为什么不应该这样做,或者我应该尝试什么有任何想法,我很想听听他们的意见。

如果有人好奇,我将其实现为 WordPress 过滤器,如下所示:

function use_straight_join( $distinct_clause ) {

    $distinct_clause = ( $use_straight_join ) ? 'STRAIGHT_JOIN' . $distinct_clause : $distinct_clause;

    return $distinct_clause;
}
add_filter( 'posts_distinct', 'use_straight_join' );

为了完整起见,这里是使用STRAIGHT_JOIN 时查询的EXPLAIN 输出。再一次,我很困惑。旧查询仅使用了 refeq_ref,据我所知,这比 range 快,但由于某种原因,这要快几个数量级。

+-----+--------------+------------------------+--------+---------------------------+-------------------+----------+-----------------+-------+----------------------------------------------+
| id  | select_type  |         table          | type   |      possible_keys        |       key         | key_len  |      ref        | rows  |                    Extra                     |
+-----+--------------+------------------------+--------+---------------------------+-------------------+----------+-----------------+-------+----------------------------------------------+
|  1  | SIMPLE       | wp_posts               | range  | PRIMARY,type_status_date  | type_status_date  |     124  | NULL            |    6  | Using where; Using temporary; Using filesort |
|  1  | SIMPLE       | wp_postmeta            | ref    | post_id,meta_key          | post_id           |       8  | db.wp_posts.ID  |    2  | Using where                                  |
|  1  | SIMPLE       | mt1                    | ref    | post_id,meta_key          | post_id           |       8  | db.wp_posts.ID  |    2  | Using where                                  |
|  1  | SIMPLE       | mt2                    | ref    | post_id,meta_key          | post_id           |       8  | db.wp_posts.ID  |    2  | Using where                                  |
|  1  | SIMPLE       | mt3                    | ref    | post_id,meta_key          | post_id           |       8  | db.wp_posts.ID  |    2  | Using where                                  |
|  1  | SIMPLE       | mt4                    | ref    | post_id,meta_key          | post_id           |       8  | db.wp_posts.ID  |    2  | Using where                                  |
|  1  | SIMPLE       | mt5                    | ref    | post_id,meta_key          | post_id           |       8  | db.mt3.post_id  |    2  | Using where                                  |
|  1  | SIMPLE       | mt6                    | ref    | post_id,meta_key          | post_id           |       8  | db.wp_posts.ID  |    2  | Using where                                  |
|  1  | SIMPLE       | wp_term_relationships  | ref    | PRIMARY,term_taxonomy_id  | PRIMARY           |       8  | db.wp_posts.ID  |    1  | Using where; Using index                     |
|  1  | SIMPLE       | tt1                    | ref    | PRIMARY,term_taxonomy_id  | PRIMARY           |       8  | db.wp_posts.ID  |    1  | Using where; Using index                     |
|  1  | SIMPLE       | tt2                    | ref    | PRIMARY,term_taxonomy_id  | PRIMARY           |       8  | db.mt1.post_id  |    1  | Using where; Using index                     |
|  1  | SIMPLE       | tt3                    | ref    | PRIMARY,term_taxonomy_id  | PRIMARY           |       8  | db.wp_posts.ID  |    1  | Using where; Using index                     |
+-----+--------------+------------------------+--------+---------------------------+-------------------+----------+-----------------+-------+----------------------------------------------+

【讨论】:

    【解决方案2】:

    你能检查“meta_value”字段是否有索引吗?这可以大大提高速度。

    我不确定,但 CAST() 函数可能会导致速度变慢?有必要吗?

    【讨论】:

    • "meta_value" 没有索引。我猜 WordPress 开发人员认为这没有必要。 CAST() 函数对于大多数 WHERE 子句都是必需的,因为它们定义了十进制精度,更重要的是,当 IN() 语句不存在时,它们似乎不会减慢任何速度。我会做一些测试来确认。
    • meta_value 上的索引本身在这里不是解决方案,因为它的结果从不直接用于比较,它总是传递给函数。
    • 我刚刚在meta_value上添加索引后重新测试了查询,我可以确认索引没有效果。另外,为了彻底起见,我在没有CAST() 函数的情况下尝试了它,不幸的是也没有效果。
    • 它不会让你的查询更漂亮,但你可以用 (... OR ... OR ...) 替换 IN() 语句
    • @dirkbonhomme,感谢您的建议。我尝试用 OR 语句替换 IN(),不幸的是,性能是相同的(执行大约 4 秒)。
    猜你喜欢
    • 1970-01-01
    • 2012-02-08
    • 2015-03-22
    • 1970-01-01
    • 1970-01-01
    • 2011-06-13
    • 2014-08-02
    • 2015-04-02
    • 1970-01-01
    相关资源
    最近更新 更多