【问题标题】:MySQL join optimization - bypassing filesortMySQL 连接优化 - 绕过文件排序
【发布时间】:2013-08-14 22:26:25
【问题描述】:

在优化 MySQL LEFT JOIN 时仍然存在问题。该查询需要 0.13 秒才能完成,而下一个查询需要 0.00 秒(简化版)。

我希望该查询达到 0.00 左右。

我已经尝试过创建索引和组合索引。变化不大。基本上只要 EXPLAIN 中有 FILESORT,它就很慢。我不确定该怎么做...跨表创建索引?它甚至存在吗?

谢谢。

罪魁祸首:

SELECT 
  SQL_NO_CACHE p.id 
FROM 1_posts p 
  INNER JOIN 1_topics t 
    ON (p.cid = t.cid && p.container = t.id) 
WHERE 
  t.cid = 1010699 
ORDER BY 
  p.id DESC 
LIMIT 1;

解释输出:

+----+-------------+-------+------+------------ --------+-------+---------+---------+-- ----+--------------------------------------------- -+
|编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 |
+----+-------------+--------+------+--------------- ----+-------+---------+---------+-- -+----------------------------------------------+
| 1 |简单 |吨 |参考 |初级,cid,cid_2 |西德 | 4 |常量 | 216 |使用索引;使用临时的;使用文件排序 |
| 1 |简单 | p |参考 |初级,cid,cid_2 | cid_2 | 8 |常量,forumdb.t.id | 12 | |
+----+-------------+--------+------+--------------- ----+-------+---------+---------+-- -+----------------------------------------------+

现在,同样的简化查询可以正常工作(使用索引等。唯一的区别在于括号之间):

SELECT 
  SQL_NO_CACHE p.id 
FROM 
  1_posts p 
  INNER JOIN 1_topics t 
    ON (p.cid = t.cid) 
WHERE 
  t.cid = 1010699 
ORDER BY 
  p.id DESC 
LIMIT 1;

解释:

+----+-------------+--------+-------+-------------- -----+---------+---------+--------+-------+-------- ------------------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+--------+-------+-------------- -----+---------+---------+--------+-------+-------- ------------------+ | 1 |简单 | p |范围 |初级,cid,cid_2 |初级 | 4 |空 | 31720 |使用哪里;使用索引 | | 1 |简单 |吨 |参考 |初级,cid,cid_2 | cid_2 | 4 |常量 | 194 |使用索引 | +----+-------------+--------+-------+-------------- -----+---------+---------+--------+-------+-------- ------------------+

表格:

CREATE TABLE `1_posts` (
  `cid` int(20) unsigned NOT NULL DEFAULT '0',
  `id` int(20) unsigned NOT NULL AUTO_INCREMENT,
  `container` int(20) unsigned NOT NULL DEFAULT '0',
  `creator` int(20) unsigned NOT NULL DEFAULT '0',
  `ref` int(20) unsigned DEFAULT NULL,
  `timestamp` int(20) unsigned NOT NULL DEFAULT '0',
  `posticon` tinyint(11) DEFAULT NULL,
  `last_edited_ts` int(10) unsigned DEFAULT NULL,
  `last_edited_by` int(20) unsigned DEFAULT NULL,
  `signature` varchar(250) DEFAULT NULL,
  `client_ip` int(10) unsigned NOT NULL DEFAULT '0',
  `data_format` tinyint(20) unsigned DEFAULT NULL,
  `use_bbcode` tinyint(3) unsigned NOT NULL DEFAULT '1',
  `use_smileys` tinyint(3) unsigned NOT NULL DEFAULT '1',
  `topic_hash` int(10) unsigned NOT NULL DEFAULT '0',
  `del_ts` int(10) unsigned NOT NULL DEFAULT '0',
  `del_reason` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`cid`,`id`),
  UNIQUE KEY `cid` (`cid`,`topic_hash`,`container`,`id`,`del_ts`),
  KEY `cid_2` (`cid`,`container`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8


CREATE TABLE `1_topics` (
  `cid` int(10) unsigned NOT NULL DEFAULT '0',
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `container` int(20) NOT NULL DEFAULT '0',
  `name` varchar(128) NOT NULL DEFAULT '',
  `creator` int(20) unsigned NOT NULL DEFAULT '0',
  `last_modified` int(20) unsigned NOT NULL DEFAULT '0',
  `views` int(11) NOT NULL DEFAULT '0',
  `closed` tinyint(3) unsigned NOT NULL DEFAULT '0',
  `sticky` tinyint(3) unsigned NOT NULL DEFAULT '0',
  `last_post_id` int(20) unsigned DEFAULT NULL,
  `num_posts` int(10) unsigned DEFAULT NULL,
  `lp_ts` int(20) unsigned NOT NULL DEFAULT '0',
  `posticon` smallint(5) unsigned DEFAULT NULL,
  `hidden` tinyint(3) unsigned NOT NULL DEFAULT '0',
  `topic_change_ts` int(10) unsigned NOT NULL DEFAULT '0',
  `topic_hash` int(10) unsigned NOT NULL DEFAULT '0',
  `forum_hash` int(10) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`cid`,`id`),
  KEY `container` (`container`),
  KEY `last_modified` (`last_modified`),
  KEY `sticky` (`sticky`),
  KEY `topic_hash` (`topic_hash`),
  KEY `forum_hash` (`forum_hash`),
  KEY `cid` (`cid`,`id`),
  KEY `cid_2` (`cid`),
  FULLTEXT KEY `name` (`name`)
) ENGINE=MyISAM AUTO_INCREMENT=211963 DEFAULT CHARSET=latin1

这是添加 Gordon 索引后的 EXPLAIN 输出:

+----+-------------+--------+------+--------------- ----------------+--------+---------+---------------- ------+--------+------------------------------------ ----------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+--------+------+--------------- ----------------+--------+---------+---------------- ------+--------+------------------------------------ ----------+ | 1 |简单 |吨 |参考 |初级,cid,cid_2 |西德 | 4 |常量 | 212 |使用索引;使用临时的;使用文件排序 | | 1 |简单 | p |参考 |初级,cid,cid_2,cid_3,cid_4 | cid_3 | 8 |常量,forumdb.t.id | 11 |使用索引 | +----+-------------+--------+------+--------------- ----------------+--------+---------+---------------- ------+--------+------------------------------------ ----------+

【问题讨论】:

    标签: mysql optimization join


    【解决方案1】:

    尝试为第二个表添加 WHERE 条件:

    SELECT
    SQL_NO_CACHE p.id
    FROM 
      1_posts p 
      INNER JOIN 1_topics t 
        ON (p.cid = t.cid) 
    WHERE 
      t.cid = 1010699 AND p.id > 0
    ORDER BY 
    p.id DESC 
    LIMIT 1;
    

    这个解决方案对我有用。

    【讨论】:

      【解决方案2】:

      这个版本使用了正确的索引:

      SELECT SQL_NO_CACHE p.id
      FROM 1_posts p INNER JOIN
           1_topics t
           ON (p.cid = t.cid)
      WHERE t.cid = 1010699
      ORDER BY p.id DESC LIMIT 1;
      

      这个版本没有:

      SELECT SQL_NO_CACHE p.id
      FROM 1_posts p INNER JOIN
           1_topics t
           ON (p.cid = t.cid && p.container = t.id);
      WHERE t.cid = 1010699
      ORDER BY p.id DESC
      LIMIT 1;
      

      首先,MySQL 可以首先将l_posts(cid, id) 上的索引用于where 子句(cid 列在索引中的第一个),然后用于连接(同一列)。然后它可以使用相同的索引进行排序——id 是索引中的下一列。 (顺便说一句,这是使用 MySQL 优化器的一个特性,它将 where 子句中的 = 条件从 t 传播到 p。)

      对于第二个,MySQL 可以将l_posts(cid, container) 索引用于wherejoin。但是,相同的索引不能用于排序。引擎认为文件排序比尝试合并两个不同的索引更好。

      要让第二个版本使用索引,请在 l_posts(cid, container, id) 上定义一个。

      【讨论】:

      • 谢谢你,戈登。您的解决方案确实改进了查询。它现在运行时间不是 0.13 秒,而是 0.04 秒,这要好很多,但仍然意味着我们正在某处进行表扫描。 EXPLAIN 仍然在 1_topics 表上提供文件排序和临时性(尽管对于 1_posts,我们现在使用新创建的 (cid,container,id) 索引。
      • 我将把 EXPLAIN 输出添加到原始帖子中。
      • 顺便说一句,在 (cid, container) 上有一个索引并且在 (cid, container, id) 上有另一个索引有什么意义吗?还是第一个索引被第二个淘汰了?
      • @Segolene 。 . .在实践中,您应该只使用具有三个字段的复合索引。如果id 是一种大型数据类型,则可能会有显着差异,但通常您不需要此类冗余索引。
      猜你喜欢
      • 2012-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-08
      • 1970-01-01
      • 2023-04-03
      • 2012-03-17
      • 2012-08-05
      相关资源
      最近更新 更多