【问题标题】:Proper index/query when using INNER JOIN使用 INNER JOIN 时的正确索引/查询
【发布时间】:2012-03-21 22:29:34
【问题描述】:

我不确定如何制作一个可以正确捕获类别/log_code 的体面的索引。也许我还需要更改我的查询?感谢您的意见!

所有 SELECTS 包含:

SELECT logentry_id, date, log_codes.log_desc FROM log_entries
INNER JOIN log_codes ON log_entries.log_code = log_codes.log_code 
ORDER BY logentry_id DESC

查询可以如上所述,但通常有一个 WHERE 来指定要显示的 log_codes 类别,和/或合作伙伴,和/或客户。 WHERE 示例:

WHERE partner_id = 1

WHERE log_codes.category_overview = 1

WHERE partner_id = 1 AND log_codes.category_overview = 1

WHERE partner_id = 1 AND customer_id = 1 AND log_codes.category_overview = 1

数据库结构:

CREATE TABLE IF NOT EXISTS `log_codes` (
  `log_code` smallint(6) NOT NULL,
  `log_desc` varchar(255),
  `category_mail` tinyint(1) NOT NULL,
  `category_overview` tinyint(1) NOT NULL,
  `category_cron` tinyint(1) NOT NULL,
  `category_documents` tinyint(1) NOT NULL,
  `category_error` tinyint(1) NOT NULL,
  PRIMARY KEY (`log_code`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `log_entries` (
  `logentry_id` int(11) NOT NULL AUTO_INCREMENT,
  `date` datetime NOT NULL,
  `log_code` smallint(6) NOT NULL,
  `partner_id` int(11) NOT NULL,
  `customer_id` int(11) NOT NULL,
  PRIMARY KEY (`logentry_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;

编辑:在字段上添加索引,这里是 SHOW INDEXES 的输出:

+-----------+------------+----------- ---------+-------------+---------- -+-----------+-------------+----------+--------+-- ----+------------+---------+----------------+
|表 |非唯一 |键名 | Seq_in_index |列名 |整理 |基数|子部分 |包装 |空 |索引类型 |评论 |索引评论 |
+-----------+------------+------------------------+ --------------+-----------+------------ +-------------+---------+--------+------+-------- ----+----------+--------------+
|日志代码 | 0 |初级 | 1 |日志代码 |一个 | 97 |空 |空 | | BTREE | | |
|日志代码 | 1 |类别邮件 | 1 |类别邮件 |一个 | 1 |空 |空 | | BTREE | | |
|日志代码 | 1 |类别概述 | 1 |类别概述 |一个 | 1 |空 |空 | | BTREE | | |
|日志代码 | 1 | category_cron | 1 | category_cron |一个 | 1 |空 |空 | | BTREE | | |
|日志代码 | 1 |类别文件 | 1 |类别文件 |一个 | 1 |空 |空 | | BTREE | | |
|日志代码 | 1 |类别错误 | 1 |类别错误 |一个 | 1 |空 |空 | | BTREE | | |
+-----------+------------+------------------------+ --------------+-----------+------------ +-------------+---------+--------+------+-------- ----+----------+--------------+

+-------------+------------+--------------+-------- --------+--------------+------------+--------------+- ---------+--------+------+------------+---------+- --------------+
|表 |非唯一 |键名 | Seq_in_index |列名 |整理 |基数|子部分 |包装 |空 |索引类型 |评论 |索引评论 |
+-------------+------------+--------------+-------- --------+--------------+------------+--------------+- ---------+--------+------+------------+---------+- --------------+
|日志条目 | 0 |初级 | 1 | logentry_id |一个 | 163020 |空 |空 | | BTREE | | |
|日志条目 | 1 |日志代码 | 1 |日志代码 |一个 | 90 |空 |空 | | BTREE | | |
|日志条目 | 1 |合作伙伴 ID | 1 |合作伙伴 ID |一个 | 6 |空 |空 |是 | BTREE | | |
|日志条目 | 1 |客户 ID | 1 |客户 ID |一个 | 20377 |空 |空 |是 | BTREE | | |
+-------------+------------+--------------+-------- --------+--------------+------------+--------------+- ---------+--------+------+------------+---------+- --------------+

编辑 2:在 log_codes 上添加了复合索引:(log_code, category_overview) 和 (log_code, category_overview)。 (customer_id, partner_id) 在 log_entries 上。

以下是一些 EXPLAIN 输出(查询返回 66818 行):

EXPLAIN SELECT log_entries.logentry_id, log_entries.date, log_codes.log_code_desc FROM log_entries
INNER JOIN log_codes ON log_entries.log_code = log_codes.log_code
WHERE log_entries.partner_id = 1 AND log_codes.category_overview = 1 ORDER BY logentry_id DESC
+----+-------------+-------------+--------+---- ---------------------------------+------------+--- ------+----------+--------+---------- ------------------+
|编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 |
+----+-------------+-------------+--------+------- ------------------+------------+------ ---+----------+--------+-------------- ---------------+
| 1 |简单 |日志条目 |参考 | log_code,partner_id |合作伙伴 ID | 2 |常量 | 156110 |使用哪里;使用文件排序 |
| 1 |简单 |日志代码 | eq_ref | PRIMARY,code_overview,overview_code |初级 | 2 | log_entries.log_code | 1 |使用位置 |
+----+-------------+-------------+--------+------- ------------------+------------+------ ---+----------+--------+-------------- ---------------+

但我也有一些我认为不会影响索引设计的 LEFT JOIN,但它们会导致“使用临时”问题。这是 EXPLAIN 输出(查询返回 66818 行):

EXPLAIN SELECT log_entries.logentry_id, log_entries.date, log_codes.log_code_desc FROM log_entries 
INNER JOIN log_codes ON log_entries.log_code = log_codes.log_code 
LEFT JOIN partners ON log_entries.partner_id = partners.partner_id 
LEFT JOIN joined_table1 ON log_entries.t1_id = joined_table1.t1_id 
LEFT JOIN joined_table2 ON log_entries.t2_id = joined_table2.t2_id 
LEFT JOIN joined_table3 ON log_entries.t3_id = joined_table3.t3_id 
LEFT JOIN joined_table4 ON joined_table3.t4_id = joined_table4.t4_id 
LEFT JOIN joined_table5 ON log_entries.t5_id = joined_table5.t5_id 
LEFT JOIN joined_table6 ON log_entries.t6_id = joined_table6.t6_id
WHERE log_entries.partner_id = 1 AND log_codes.category_overview = 1 ORDER BY logentry_id DESC;
+----+-------------+---------------+--------+-- ------------------------------------------------+-------------- -+---------+--------------+------+---- ----------------------------------------------------------+
|编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 |
+----+-------------+---------------+--------+----- --------------------------------+---------------+- --------+--------------+------+------ ----------------------------------------------------+
| 1 |简单 |日志代码 |参考 | PRIMARY,code_overview,overview_code |概览代码 | 1 |常量 | 54 |使用哪里;使用临时的;使用文件排序 |
| 1 |简单 |日志条目 |参考 | log_code,partner_id |日志代码 | 2 | log_codes.log_code | 1811 |使用位置 |
| 1 |简单 |合作伙伴 |常量 |初级 |初级 | 2 |常量 | 1 |使用索引 |
| 1 |简单 |加入表1 | eq_ref |初级 |初级 | 1 | log_entries.t1_id | 1 |使用索引 |
| 1 |简单 |加入表2 | eq_ref |初级 |初级 | 1 | log_entries.t2_id | 1 |使用索引 |
| 1 |简单 |加入表3 | eq_ref |初级 |初级 | 3 | log_entries.t3_id | 1 | |
| 1 |简单 |加入表4 | eq_ref |初级 |初级 | 3 |加入表3.t4_id | 1 |使用索引 |
| 1 |简单 |加入表5 | eq_ref |初级 |初级 | 4 | log_entries.t5_id | 1 |使用索引 |
| 1 |简单 |加入表6 | eq_ref |初级 |初级 | 4 | log_entries.t6_id | 1 |使用索引 |
+----+-------------+---------------+--------+----- --------------------------------+---------------+- --------+--------------+------+------ ----------------------------------------------------+

不知道这是个好主意还是坏主意,但子查询似乎摆脱了“使用临时”。这是两种常见情况的 EXPLAIN 输出。此查询返回 66818 行:

EXPLAIN SELECT log_entries.logentry_id, log_entries.date, log_codes.log_code_desc FROM log_entries INNER JOIN log_codes ON log_entries.log_code = log_codes.log_code
WHERE log_entries.partner_id = 1
AND log_entries.log_code IN (SELECT log_code FROM log_codes WHERE category_overview = 1) ORDER BY logentry_id DESC;
+----+--------+-------------+------ ------------+--------------------------------------+ ------------+---------+----------+---- --+------------------+
|编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 |
+----+--------+-------------+--------- --------+-------------------------------------------------+--- ---------+---------+----------+------ -+--------------+
| 1 |初级 |日志条目 |参考 | log_code,partner_id |合作伙伴 ID | 2 |常量 | 156110 |使用哪里;使用文件排序 |
| 1 |初级 |日志代码 | eq_ref | PRIMARY,code_overview |初级 | 2 | log_entries.log_code | 1 | |
| 2 |依赖子查询 |日志代码 |唯一子查询 | PRIMARY,code_overview,overview_code |初级 | 2 |功能 | 1 |使用位置 |
+----+--------+-------------+--------- --------+-------------------------------------------------+--- ---------+---------+----------+------ -+--------------+

以及关于客户的概述,查询返回 12 行:

EXPLAIN SELECT log_entries.logentry_id, log_entries.date, log_codes.log_code_desc FROM log_entries INNER JOIN log_codes ON log_entries.log_code = log_codes.log_code
WHERE log_entries.partner_id = 1 AND log_entries.customer_id = 10000
AND log_entries.log_code IN (SELECT log_code FROM log_codes WHERE category_overview = 1) ORDER BY logentry_id DESC;
+----+--------+-------------+------ -----------+---------------------------------------- ------------+-------------+---------+------------ ----------+------+------------------+
|编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 |
+----+--------+-------------+--------- --------+----------------------------------------- ---------+--------------+---------+--------------- --------+------+------------------+
| 1 |初级 |日志条目 |参考 | log_code,partner_id,customer_id,customer_partner |客户 ID | 4 |常量 | 27 |使用哪里;使用文件排序 |
| 1 |初级 |日志代码 | eq_ref | PRIMARY,code_overview |初级 | 2 | log_entries.log_code | 1 | |
| 2 |依赖子查询 |日志代码 |唯一子查询 | PRIMARY,code_overview,overview_code |初级 | 2 |功能 | 1 |使用位置 |
+----+--------+-------------+--------- --------+----------------------------------------- ---------+--------------+---------+--------------- --------+------+------------------+

【问题讨论】:

  • 复合索引的正确结构取决于基数。如果空间不是问题,那么使用多个索引可能会更好,以最好地服务于这些不同的查询。请在为每个相关字段添加单列索引后发布 SHOW INDEXES 的输出?
  • 谢谢!我向相关字段添加了单列索引(感谢@Shiplu)。 SHOW INDEXES 的输出添加到我的问题中。在测试 WHERE category_overview=1 查询时使用 category_overview 索引。但是在 SHOW EXPLAIN 上,我在 log_codes 表上得到“使用 where;使用临时;使用文件排序”,这似乎是一个主要瓶颈。

标签: mysql indexing


【解决方案1】:

在索引编制方面没有保证成功的简单规则 - 您需要查看一个合理的典型调用时间段,以确定哪些有助于提高性能。

因此,所有后续的 cmets 都不应被视为绝对规则:

如果一个索引可以快速让您找到一小部分数据,而不是仅消除一半数据(例如,在只有 M/ 的性别列上的索引中很少有值F 作为可能的条目)。那么,例如内部的值有多独特。 log_code、category_overview 和 partner_id?

对于给定的查询,拥有一个“覆盖”索引通常很有帮助,即包含查询使用的所有字段的索引 - 但是,如果查询中单个表中的字段过多,您可以而是需要一个包含“where”或“join”子句中的字段的索引来识别行,然后连接回表存储以获取所需的所有字段。

因此,根据您提供的信息,log_codes 的候选索引将包括 log_code 和 category_overview。对于 log_code 和 partner_id,在 log_entries 上也是如此。但是,需要评估它们对性能的影响。

请记住,任何给定的索引都可以提高单个查询检索数据的读取性能,但它也会减慢对表的写入速度,然后需要写入更多信息,即新行适合附加指数。这就是为什么您需要查看数据库活动的大图来确定索引的价值所在。

【讨论】:

  • 很好的回复! log_codes:100 行,log_entries:150000 行。 5 个不同的合作伙伴 ID。 log_codes 中有 5 个不同的 category_XXXX 列。 category_overview:30 个代码,60000 个条目。默认日志视图:category_overview=1 和 partner_id 集。客户的默认日志视图:customer_id 也已设置。忘了提到 ORDER BY logentry_id DESC。尝试了关于 log_entries 的索引(partner_id、log_code、logentry_id)。在 log_codes (log_code, category_overview) 上有或没有索引,我在 log_codes 上得到“使用 where;使用临时;使用文件排序”,“复制到 Tmp 表”需要 1900 毫秒,占查询时间的 90%。
【解决方案2】:

非常感谢您抽出时间根据要求的详细信息更新您的问题。如果这听起来很傲慢,我很抱歉,但不准备花时间帮助自己的人的数量令人惊讶。

在 log_entries 表上的 (customer_id, partner_id) 中添加复合索引应该会给您的最后一个示例 where 子句带来显着的好处。

log_codes 表的 SHOW INDEXES 的输出表明它当前未填充,因为它显示除了 PK 之外的所有内容都为 NULL。是这样吗?

编辑对不起。只需阅读您对 KAJ 详细表格内容的答案的评论即可。可能值得再次运行 SHOW INDEXES 语句,因为看起来 MySQL 可能正在构建它的统计信息。

为 log_codes 表添加跨 (log_code, category_overview) 的复合索引应该会有所帮助,但您需要检查解释输出以查看它是否正在使用。

作为一个非常粗略的一般规则,您希望从具有最高基数的列开始创建复合索引,但情况并非总是如此。它将在很大程度上取决于数据分布和查询结构。

更新我已经为您的数据集创建了一个模型并添加了以下索引。它们根据您的示例 WHERE 子句进行了显着改进 -

ALTER TABLE `log_codes`
    ADD INDEX `IX_overview_code` (`category_overview`, `log_code`);

ALTER TABLE `log_entries`
    ADD INDEX `IX_partner_code` (`partner_id`, `log_code`),
    ADD INDEX `IX_customer_partner_code` (`customer_id`, `partner_id`, `log_code`);

就磁盘空间和插入性能下降而言,最后一个索引非常昂贵,但根据您的最终 WHERE 子句示例,它提供了非常快的 SELECT。我的示例数据集在 log_entries 表中有超过 100 万条记录,在合作伙伴和客户 ID 之间分布相当均匀。您的三个示例 WHERE 子句在不到一秒的时间内执行,但以 category_overview 作为唯一标准的子句非常慢,尽管仍然只有 200k 行的亚秒级。

【讨论】:

  • 是的,再次运行 SHOW INDEXES 并获得所有类别的基数 1(值始终为 1 或 0)。还发现如果我删除 ORDER BY,它不会在 log_code 上执行“使用临时”,并且查询在 0.0023ms 而不是 2s 上执行......!有什么办法解决这个问题吗?尽管它很粗糙,但该一般规则会有所帮助。我刚开始学习索引,并认为应该先学习低基数的索引[例如。 (partner_id, customer_id)]...
  • 添加了跨(log_code,category_overview)的复合索引。它显示在@possible_keys@ 中,但在@key@ 中没有使用任何键。看起来从未使用过 (customer_id, partner_id) 的综合索引;当在 WHERE 中指定了 customer_id 和 partner_id 时,它使用 (customer_id),当在 WHERE 中仅指定了 partner_id 时,它使用 (partner_id)。我应该删除单个索引吗?
  • 如果索引基数非常低(
  • 这可能无济于事,但您能否将 PROCEDURE ANALYSE() 的输出发布到这两个表上?
  • 感谢您的耐心等待,nnichols!我从问题中省略了 7 个 LEFT JOIN 以简化可读性,因为我认为这些不会影响 WHERE/ORDER BY。示例LEFT JOIN customer_status_codes ON log_entries.new_customer_status_code = customer_status_codes.customer_status_code。似乎如果我最多有 4-5 个,log_codes 上的“使用临时”消失并且查询执行速度很快。它看起来不像是导致问题的一个特定连接。 EXPLAIN 显示在所有连接的表中使用了 PRIMARY 键并且 rows=1。
猜你喜欢
  • 2017-03-30
  • 2012-11-04
  • 1970-01-01
  • 1970-01-01
  • 2017-04-24
  • 1970-01-01
  • 1970-01-01
  • 2020-09-19
  • 1970-01-01
相关资源
最近更新 更多