具有巨大表的MySQL:2个查询比1快(索引未使用)

时间:2012-12-03 20:47:46

标签: mysql sql indexing

我有两张桌子:

CREATE TABLE `linf` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `glorious` bit(1) DEFAULT NULL,
  `limad` varchar(127) COLLATE utf8_bin DEFAULT NULL,
  `linfDetails_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`ID`),
  KEY `FK242415D3B0D13C` (`linfDetails_id`),
  CONSTRAINT `FK242415D3B0D13C` FOREIGN KEY (`linfDetails_id`) REFERENCES `linfdetails` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=135111 DEFAULT CHARSET=utf8 COLLATE=utf8_bin

(130K行)

CREATE TABLE `messageentry` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `mboxOffset` bigint(20) DEFAULT NULL,
  `mboxOffsetEnd` bigint(20) DEFAULT NULL,
  `from_id` bigint(20) DEFAULT NULL,
  `linf_ID` bigint(20) DEFAULT NULL,
  `mailSourceFile_id` bigint(20) DEFAULT NULL,
  `messageDetails_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FKBBB258CB60B94D38` (`mailSourceFile_id`),
  KEY `FKBBB258CB11F9E114` (`from_id`),
  KEY `FKBBB258CBF7C835B8` (`messageDetails_id`),
  KEY `FKBBB258CBB10E8518` (`linf_ID`),
  CONSTRAINT `FKBBB258CBB10E8518` FOREIGN KEY (`linf_ID`) REFERENCES `linf` (`ID`),
  CONSTRAINT `FKBBB258CB11F9E114` FOREIGN KEY (`from_id`) REFERENCES `emailandname` (`id`),
  CONSTRAINT `FKBBB258CB60B94D38` FOREIGN KEY (`mailSourceFile_id`) REFERENCES `mailsourcefile` (`id`),
  CONSTRAINT `FKBBB258CBF7C835B8` FOREIGN KEY (`messageDetails_id`) REFERENCES `messagedetails` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5888892 DEFAULT CHARSET=utf8 COLLATE=utf8_bin

(5M行)

我需要通过linf.limad找到linf,然后查找与此linf对应的所有消息。

如果我在两个查询中选择它:

select sql_no_cache l.id from linf l where l.limad='test@';
select sql_no_cache me.* from messageentry me where me.linf_id = 118668;

然后需要0.06秒。

如果我使用

select sql_no_cache me.* from messageentry me where me.linf_id in(
select l.id from linf l where l.limad='test@') ;

执行需要10秒。这一个:

select sql_no_cache me.* from messageentry me, linf l where me.linf_id=l.id
and l.limad='test@';

需要4秒。 (时间稳定)

此请求返回0结果,因为此linf没有消息。 事实上,我已经从大请求中删除了这个

select messageent1_.*
from
    MailSourceFile mailsource0_,        
    MessageEntry messageent1_ ,
    MessageDetails messagedet2_,    
    Linf linf3_
where
    messageent1_.messageDetails_id = messagedet2_.id
        and messageent1_.linf_ID = linf3_.ID
        and linf3_.limad = 'test@'
and mailsource0_.id = messageent1_.mailSourceFile_id

工作~1分钟。那不是太多了吗?说明没有使用messageEntries索引:

mysql> explain select sql_no_cache me.* from messageentry me, linf l where me.linf_id=l.id and l.limad='test@';
+----+-------------+-------+--------+--------------------+---------+---------+------------------+---------+-------------+
| id | select_type | table | type   | possible_keys      | key     | key_len | ref              | rows    | Extra       |
+----+-------------+-------+--------+--------------------+---------+---------+------------------+---------+-------------+
|  1 | SIMPLE      | me    | ALL    | FKBBB258CBB10E8518 | NULL    | NULL    | NULL             | 5836332 |             |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY            | PRIMARY | 8       | skryb.me.linf_ID |       1 | Using where |
+----+-------------+-------+--------+--------------------+---------+---------+------------------+---------+-------------+

任何想法为什么?我已经获得了mysql~1.6 G的内存,这应该适合所有表格。

感谢名单。

4 个答案:

答案 0 :(得分:3)

让我们看一下查询:

select sql_no_cache me.*
from messageentry me, linf l
where me.linf_id=l.id
and l.limad='test@';

它做什么?根据{{​​1}}对EXPLAIN表中每一行的执行计划,它检查me中是否有相应的记录。由于您在linf字段上没有任何索引,MySQL 5M次从磁盘(而不是从内存)中获取limad字段的值以检查它是否等于'@test'。您说该查询返回0行,但对于另一个limad值会产生更多行,它需要在磁盘上显示所有limad个字段。

好的,me.*字段是limad,这是一个索引,它可能是有意义的(无论如何我会添加它)。 130k行少于5M,因此从varchar(127) COLLATE utf8_bin开始会很棒,而linf开头的所有内容都是messageentry。为什么只有那些领域?由于我们将再进行两次连接,并且我们不从连接表中获取数据,因此表似乎缩小了最终结果集,即查询框架需要它们。我们只从它们开始:

id, mailSourceFile_id, messageDetails_id

查询选择所需的linf_ID,然后为每个找到的ID查找SELECT me.id, me.mailSourceFile_id, me.messageDetails_id FROM ( SELECT ID as linf_ID FROM linf WHERE limad='test@' ) as linf JOIN messageentry me USING (linf_ID); 中的apprepriate行。由于linf_iD上有索引,因此查询的结果应该超过4秒。

但是那些messageentry不能从内存中获取,因为MySQL需要进行复杂的索引合并,因此,MySQL无论如何都要在磁盘上为匹配linf_ID的每一行进行操作。如果您的索引一次包含所有这三个字段,那么在有大量行被后续连接过滤的情况下,查询会更快。

如果您将KEY me.mailSourceFile_id, me.messageDetails_id更新为FKBBB258CBB10E8518 (linf_ID),则会有这样的索引。

结果查询类似于:

FKBBB258CBB10E8518 (linf_ID, mailSourceFile_id, messageDetails_id)

实际上,只要您按照上面的建议更新索引SELECT me.* FROM ( SELECT ID as linf_ID FROM linf WHERE limad='test@' ) as linf JOIN messageentry me USING (linf_ID) JOIN MailSourceFile ms ON ms.id = me.mailSourceFile_id JOIN MessageDetails md ON md.id = me.messageDetails_id; ,原始查询很可能会与上一次查询具有相同的执行计划和时间。

答案 1 :(得分:0)

如果明确定义连接条件会发生什么情况呢?

select sql_no_cache me.* 
from messageentry me JOIN linf l ON  me.linf_id=l.id
WHERE l.limad='test@';

如果优化程序选择进行交叉连接或其他奇怪的操作,您可能会对您的版本感到困惑。

除此之外,您可以考虑使用力量指数:

select sql_no_cache me.* 
from messageentry me FORCE INDEX (FKBBB258CBB10E8518)
JOIN linf l ON  me.linf_id=l.id         
WHERE l.limad='test@';

这至少会告诉你索引是否真的会帮助你。

答案 2 :(得分:0)

MySQL在in子句中使用子查询做得很差,解释了你在那里看到的糟糕表现。我怀疑连接性能与连接的顺序有关。它可能完整地阅读了消息表。

尝试将in版本更改为exists

select sql_no_cache me.*
from messageentry me
where exists (select 1 from linf l where l.limad='test@' and l.id = me.inf_id limit 1) ;

顺便说一句,你应该习惯于在on子句中而不是在where子句中进行连接。

答案 3 :(得分:0)

  • 如果可能,请尝试使用INT而不是BIGINT,如果可能,也请为主键选择INT。 像“linf_ID”这样的二级索引将它的相关主键存储在磁盘中。 使用BIGINT意味着更多的页面错误和磁盘读取。 http://planet.mysql.com/entry/?id=13825

  • 要减少varchar的索引大小,请尝试使用limad的索引部分 在书中“High Performance Mysql 3Edition”给我们一种选择varchar索引长度的方法。选择让以下两个sql的结果类似的长度

    SELECT COUNT(DISTINCT city)/ COUNT(*)FROM sakila.city_demo;

    SELECT COUNT(DISTINCT LEFT(city,3))/ COUNT()AS sel3,COUNT(DISTINCT LEFT(city,4))/ COUNT()AS sel4,COUNT(DISTINCT LEFT(城市,5))/ COUNT()AS sel5,COUNT(DISTINCT LEFT(city,6))/ COUNT()AS sel6,COUNT(DISTINCT LEFT(city,7))/ COUNT(* )AS sel7 FROM sakila.city_demo;

  • 让MySQL分析并优化磁盘中的数据 http://dev.mysql.com/doc/refman/5.1/en/optimize-table.html http://dev.mysql.com/doc/refman/5.0/en/analyze-table.html

  • 为了你的1分钟运行“大请求”SQL问题,优化这个SQL, 您需要使用多列索引。 http://dev.mysql.com/doc/refman/5.0/en/multiple-column-indexes.html

    在MessageEntry上创建UNIQUE INDEX idx_name(linf_ID,messageDetails_id,mailSourceFile_id)