具有大型表的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;