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

具有大型表的MySQL:2次查询比1次快(未使用索引),mysql,sql,indexing,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

我有两张桌子:

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行)

(5米行)

我需要通过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.6g内存,这应该适合所有表


Thanx.

如果显式定义联接条件,会发生什么

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@';

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

MySQL在子句中的
子查询方面做得很差,解释了您在那里看到的糟糕性能。我怀疑联接性能与联接的顺序有关。它可能正在完整地读取messages表

尝试将
版本中的
更改为存在的

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) ;
顺便说一句,你应该习惯于在
子句中而不是在
where
子句中进行连接。

  • 如果可能,尝试使用INT代替BIGINT,如果可能,还可以选择INT作为主键。 像“linf_ID”这样的辅助索引将其相关的主键存储在磁盘中。 使用BIGINT意味着更多的页面错误和磁盘读取。

  • 要减小varchar的索引大小,请尝试limad的索引部分。
    在《高性能Mysql 3Edition》一书中,我们给出了一种选择varchar索引长度的方法。选择使以下两个sql的结果相似的长度

    从sakila.city\u demo中选择COUNT(独立城市)/COUNT(*)

    从sakila.city\u demo中选择COUNT(DISTINCT LEFT(city,3))/COUNT()作为sel3,COUNT(DISTINCT LEFT(city,4))/COUNT()作为sel4,COUNT(DISTINCT LEFT(city,5))/COUNT()作为sel5,COUNT(DISTINCT LEFT(city,6))/COUNT()作为sel6,COUNT(DISTINCT LEFT(city,7))/COUNT(*)作为sel7

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

  • 对于有问题的1分钟“大请求”SQL,要优化此SQL, 您需要使用多列索引

    在MessageEntry上创建唯一索引idx\u名称(linf\u ID、messageDetails\u ID、mailSourceFile\u ID)


让我们看看这个查询:

select sql_no_cache me.*
from messageentry me, linf l
where me.linf_id=l.id
and l.limad='test@';
它有什么作用?根据
EXPLAIN
中的执行计划,针对
me
表中的每一行,它检查
linf
中是否有相应的记录。由于在
limad
字段上没有任何索引,MySQL会多次从磁盘(而不是内存)获取
limad
字段的值,以检查它是否等于'@test'。您说查询返回0行,但对于另一个
limad
值,它需要为所有
me.*
字段提供更多的行,它需要在磁盘上运行

好的,
limad
字段是
varchar(127)COLLATE utf8\u bin
,这是一个索引,它可能很昂贵(无论如何我都会添加它)。130k行少于5M,因此最好从
linf
开始,我们需要从
messageentry
开始,只需要
id、mailSourceFile\u id、messageDetails\u 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);
查询选择所需的linf_ID as,然后在
messageentry
中为每个找到的ID查找相应的行。由于您在linf_iD上有一个索引,因此查询的结果应该快于4秒

但是那些
me.mailSourceFile\u id,me.messageDetails\u id
不能从内存中获取,因为MySQL需要进行复杂的索引合并,因此,MySQL无论如何都会在磁盘上为具有匹配linf\u id的每一行创建索引。如果您的索引同时包含所有这三个字段,如果有大量的行被后续连接过滤,那么查询会更快

如果您将密钥
fkbb258cbb10e8518(linf_ID)
更新为
fkbb258cbb10e8518(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;

实际上,只要您按照上面的建议更新索引
fkbb258cbb10e8518(linf_ID)
,您的原始查询很可能与上一个查询具有相同的执行计划和时间。

首先,您是否对
linf_ID
limad
列进行了索引?其次,当有0行时,进行2个单表查询总是更快,因为不需要/不需要联接。选择一个实际返回多个结果的id并比较这些时间…不要期望“IN”子句很快,您最好使用join。您可以从explain和表定义中看到,linf_id具有键FKBB258CBB10E8518(mysql也在索引中显示它)。同样,limad.JOIN也太慢,它不使用索引…这一次需要14秒,即它比IN或JOIN更慢。在这种情况下,即使我试图通过使用索引强制使用键(FKBB258CBB10E8518):此查询:从messageentry me use index(FKBB258CBB10E8518)中选择sql_no_cache me.*使用索引(FKBB258CBB10E8518)加入linf l ON me.linf_id=l.id其中l.limad='test@;仍然不使用FKBB258CBB10E8518还有一种强制索引风格,带有强制索引的查询与不带强制索引的查询行为完全相同,并且具有相同的解释
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;