非常慢的查询: <derived2> 使用临时的;使用文件排序

Posted

技术标签:

【中文标题】非常慢的查询: <derived2> 使用临时的;使用文件排序【英文标题】:Very slow query with: <derived2> Using temporary; Using filesort 【发布时间】:2013-09-08 21:54:17 【问题描述】:

我的选择类别查询 Restructuring a DB for best performance 出现性能问题 所以我设定了一个目标来解决它。但最终结果证明这是一个更复杂的查询,与原始查询相比,性能实际上有所下降。

SELECT *
FROM   post
       LEFT JOIN post_plus
         ON ( post.id = post_plus.news_id )
       INNER JOIN (SELECT DISTINCT c1.postid
                  FROM   post_category c1
                         JOIN post_category c2
                           ON c1.postid = c2.postid
                  WHERE  c1.categoryid IN ( 130, 3, 4, 5 )
                         AND c2.categoryid = 73) post_category 
         ON ( post_category.postid = post.id ) 
WHERE  approve = 1
ORDER  BY fixed DESC,
          date DESC     
LIMIT  0, 7;           

新查询需要:(1.02 秒)- 使用正则表达式的旧查询耗时(0.23 秒)

我只能猜测是因为Using temporary; Using filesort

我怎样才能摆脱这个?

解释查询:

`+----+-------------+---------------+--------+-------------------+------------+---------+--------------------------+-------+----------------------------------------+
| id | select_type | table         | type   | possible_keys     | key        | key_len | ref                      | rows  | Extra                                  |
+----+-------------+---------------+--------+-------------------+------------+---------+--------------------------+-------+----------------------------------------+
|  1 | PRIMARY     | <derived2>    | ALL    | NULL              | NULL       | NULL    | NULL                     | 10470 | Using temporary; Using filesort        |
|  1 | PRIMARY     | post      | eq_ref | PRIMARY,approve   | PRIMARY    | 4       | post_category.postid |     1 | Using where                            |
|  1 | PRIMARY     | post_plus | ref    | news_id           | news_id    | 5       | post_category.postid |     1 | NULL                                   |
|  2 | DERIVED     | c1            | range  | postId,categoryId | categoryId | 2       | NULL                     | 10470 | Using index condition; Using temporary |
|  2 | DERIVED     | c2            | ref    | postId            | postId     | 4       | online_test.c1.postId    |     1 | Using index                            |
+----+-------------+---------------+--------+-------------------+------------+---------+--------------------------+-------+----------------------------------------+

`

张贴表

| post | CREATE TABLE `post` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `autor` varchar(40) NOT NULL DEFAULT '',
  `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `short_story` text NOT NULL,
  `full_story` text NOT NULL,
  `xfields` text NOT NULL,
  `title` varchar(255) NOT NULL DEFAULT '',
  `descr` varchar(200) NOT NULL DEFAULT '',
  `keywords` text NOT NULL,
  `category` varchar(200) NOT NULL DEFAULT '0',
  `alt_name` varchar(200) NOT NULL DEFAULT '',
  `comm_num` mediumint(8) unsigned NOT NULL DEFAULT '0',
  `allow_comm` tinyint(1) NOT NULL DEFAULT '1',
  `allow_main` tinyint(1) unsigned NOT NULL DEFAULT '1',
  `approve` tinyint(1) NOT NULL DEFAULT '0',
  `fixed` tinyint(1) NOT NULL DEFAULT '0',
  `allow_br` tinyint(1) NOT NULL DEFAULT '1',
  `symbol` varchar(3) NOT NULL DEFAULT '',
  `tags` varchar(255) NOT NULL DEFAULT '',
  `metatitle` varchar(255) NOT NULL DEFAULT '',
  `FileTempUUID` varchar(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `autor` (`autor`),
  KEY `alt_name` (`alt_name`),
  KEY `category` (`category`),
  KEY `allow_main` (`allow_main`),
  KEY `date` (`date`),
  KEY `symbol` (`symbol`),
  KEY `comm_num` (`comm_num`),
  KEY `tags` (`tags`),
  KEY `descr` (`descr`),
  KEY `title` (`title`),
  KEY `approve` (`approve`,`allow_main`),
  FULLTEXT KEY `full_story` (`full_story`),
  FULLTEXT KEY `short_story` (`short_story`,`full_story`,`xfields`,`title`),
  FULLTEXT KEY `title_2` (`title`)
) ENGINE=InnoDB AUTO_INCREMENT=32586 DEFAULT CHARSET=utf8 |

post_plus

| post_plus | CREATE TABLE `post_plus` (
  `pid` int(11) NOT NULL AUTO_INCREMENT,
  `news_id` int(11) DEFAULT NULL,
  `kp_id` varchar(100) CHARACTER SET utf8 DEFAULT NULL,
  `pdate` datetime DEFAULT NULL,
  `news_read` int(11) NOT NULL DEFAULT '0',
  `user_id` int(11) NOT NULL DEFAULT '0',
  `allow_rate` tinyint(1) NOT NULL DEFAULT '1',
  `rating` mediumint(8) NOT NULL DEFAULT '0',
  `vote_num` mediumint(8) NOT NULL DEFAULT '0',
  `votes` tinyint(1) NOT NULL DEFAULT '0',
  `editdate` int(11) DEFAULT NULL,
  `view_edit` tinyint(1) NOT NULL DEFAULT '0',
  `editor` varchar(40) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `reason` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '',
  `access` varchar(150) CHARACTER SET utf8 NOT NULL DEFAULT '',
  PRIMARY KEY (`pid`),
  KEY `user_id` (`user_id`),
  KEY `news_id` (`news_id`)
) ENGINE=InnoDB AUTO_INCREMENT=32819 DEFAULT CHARSET=latin1 |

post_category 表

| post_category | CREATE TABLE `post_category` (
  `cid` bigint(11) NOT NULL AUTO_INCREMENT,
  `postId` int(11) NOT NULL,
  `categoryId` smallint(6) NOT NULL,
  PRIMARY KEY (`cid`),
  KEY `postId` (`postId`,`categoryId`),
  KEY `categoryId` (`categoryId`)
) ENGINE=InnoDB AUTO_INCREMENT=100870 DEFAULT CHARSET=latin1 |

【问题讨论】:

真的 - 除了 EXPLAIN 计划 - 你应该显示所有表的定义(SHOW CREATE TABLE tablename; 的输出),以便我们知道数据类型、主键和外键、索引。 你能添加一个(categoryId, postId)索引并更新效率和新的解释吗? 为什么第二个LEFT JOIN(到派生表)首先是LEFT连接?看起来应该是 INNER 加入。 是的,它是内在的。复制了旧代码...我在 post_category 表中有 KEY postId (postId,categoryId), KEY categoryId (categoryId) ` 的键 (categoryId, postId) 上再添加一个、复合、索引,您可以删除(categoryId) 一个。 【参考方案1】:

我认为这个查询在精神上是等效的,并且可能执行得更快。特别是如果您在 post_category (postid, categoryid) 上创建索引

Select
    *
From
    post
        Left Join
    post_plus
        On post.id = post_plus.news_id
Where
    Exists (
        Select
            'x'
        From
            post_category c1
        Where
            categoryid = 73 and
            c1.postid = post.postid
    ) And
    Exists (
        Select
            'x'
        From
            post_category c2
        Where
            categoryid In (130, 3, 4, 5) and
            c2.postid = post.postid
    ) And
    approve = 1
Order By
    fixed Desc,
    date Desc
Limit
    0, 7;

由于postid, categoryidpost_category 上是唯一的,您也可以这样重写它。它可能会受益于相反顺序的索引(categoryid, postid)

Select
    post.*,
    post_plus.*
From
    post_category c1
        Inner Join
    post
        On c1.categoryid = 73 and c1.postid = post.postid
        Left Join
    post_plus
        On post.id = post_plus.news_id
Where
    Exists (
        Select
            'x'
        From
            post_category c2
        Where
            categoryid In (130, 3, 4, 5) and
            c2.postid = post.postid
    ) And
    approve = 1
Order By
    fixed Desc,
    date Desc
Limit
    0, 7;

【讨论】:

【参考方案2】:

由于每个帖子(最多)只有一个 post_plus,因此您可以先限制,然后将连接应用到 post_plus,使用您的查询或 Laurence 的重写(或任何其他等效项)。以下是 Laurence 的查询以这种方式重写并稍作修改:

Select
    p.*, pp.*
From
  ( Select
        post.id
    From
        post
    Where
        Exists (
            Select
                'x'
            From
                post_category As c1
            Where
                c1.categoryid = 73 And
                c1.postid = post.postid
        ) And
        Exists (
            Select
                'x'
            From
                post_category As c2
            Where
                c2.categoryid In (130, 3, 4, 5) And
                c2.postid = post.postid
        ) And
        post.approve = 1
    Order By
        post.fixed Desc,
        post.date Desc
    Limit
        0, 7
  ) As lim
      Join
    post AS p
        On lim.id = p.id 
      Left Join
    post_plus As pp
        On lim.id = pp.news_id
Order By
    p.fixed Desc,
    p.date Desc  ;

【讨论】:

你们太棒了! (0.06 秒)

以上是关于非常慢的查询: <derived2> 使用临时的;使用文件排序的主要内容,如果未能解决你的问题,请参考以下文章

Vuefire(Firestore)非常非常慢的查询

Orbeon - 非常慢的xml大数据导入[加载并保存]

MySQL:非常慢的更新/插入/删除查询挂在“查询结束”步骤

带有多个 JOIN 的非常慢的 PSQL 查询

如何优化这个非常慢的 MySQL 查询?

非常慢的 MySQL COUNT DISTINCT 查询,即使有索引——如何优化?