我有两张桌子:
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的内存,这应该适合所有表格。
感谢名单。
答案 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)