3

我有两张桌子:

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

4 回答 4

3

让我们看一下查询:

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 5M 次limad从磁盘(而不是内存)中获取字段的值以检查它是否等于“@test”。您说查询返回 0 行,但是对于另一个limad可以提供更多行的值,它需要为所有me.*字段在磁盘上进行。

好的,limad字段 is varchar(127) COLLATE utf8_bin,这是它的索引可能很昂贵(无论如何我都会添加它)。130k 行小于 5M,所以从 开始会很棒linf,而我们messageentry一开始只需要id, mailSourceFile_id, messageDetails_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,然后为每个找到的 id 查找适当的行messageentry。由于您在 linf_iD 上有一个索引,因此查询的结果应该快于 4 秒。

但是这些me.mailSourceFile_id, me.messageDetails_id不能从内存中取出,因为 MySQL 需要进行复杂的索引合并,因此,无论如何,MySQL 都会为具有匹配 linf_ID 的每一行进入磁盘。如果您有一个同时包含所有这三个字段的索引,则查询会更快,以防有大量行被后续连接过滤。

如果您将 KEY 更新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;

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

于 2012-12-07T17:25:02.293 回答
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@';

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

于 2012-12-03T21:08:04.307 回答
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(city, 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/分析表.html

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

    在 MessageEntry 上创建唯一索引 idx_name(linf_ID,messageDetails_id,mailSourceFile_id)

于 2012-12-07T15:03:29.817 回答
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子句中进行连接。

于 2012-12-03T21:09:47.887 回答