2010-06-23 54 views
2

两张桌子。帮我把SUBQUERY变成JOIN

电子邮件 id(int10)|所有权(int10)

消息 emailid(int10)索引|消息(中文文本)

子查询(这在MySQL中很糟糕)。

SELECT COUNT(*)FROM消息 WHERE消息LIKE '%字%' AND EMAILID IN(SELECT ID从电子邮件WHERE所有权= 32)


这里的用法是,我运行一个搜索在电子邮件上(这在上面的示例中显然是简化的),其生成了3000个电子邮件ID的列表。然后,我想对消息进行搜索,因为我需要进行文本匹配 - 只有这3000封电子邮件针对邮件。

对邮件的查询很昂贵(邮件没有编入索引),但这很好,因为它只会检查几行。

想法:

i)加入。我迄今为止的尝试都没有奏效,导致消息表的全表扫描(即未使用emailid索引)ii)临时表。这可以工作,我想。 3)在客户端缓存ID并运行2个查询。这确实有用。不优雅。 iv)子查询。 mySQL子查询每次运行第二个查询,所以这不起作用。也许固定在MySQL 6.

好的,这是我到目前为止。这些是实际的字段名称(我简化了一下)。

查询:

SELECT COUNT(*) FROM ticket LEFT JOIN ticket_subject 
ON (ticket_subject.ticketid = ticket.id) 
WHERE category IN (1) 
AND ticket_subject.subject LIKE "%about%" 

结果:

1 SIMPLE ticket ref  PRIMARY,category category 4 const 28874  
1 SIMPLE ticket_subject eq_ref PRIMARY  PRIMARY  4 deskpro.ticket.id 1 Using where 

它需要0.41秒和返回的COUNT(*)113

运行:

SELECT COUNT (*) FROM ticket WHERE category IN (1) 

需要0.01秒和f共有33,000项结果。

运行

SELECT COUNT (*) FROM ticket_subject WHERE subject LIKE "%about%" 

注意到0.14秒,发现1300个结果。

票据表和ticket_subject表都有300,000行。

ticket_subject.ticketid和ticket.category上有一个索引。

我现在意识到使用LIKE语法是一个错误 - 因为它有一点关于FULLTEXT的红鲱鱼。这不是问题。问题是:

1)表A-非常快的查询,在索引上运行。 0。001秒 2)表B - 中等到慢速查询,没有索引 - 进行全表扫描。 0.1秒。

这两个结果都很好。问题是我必须加入他们,搜索需要0.3秒;这对我来说没有意义,因为表B上组合查询的慢速方面应该更快,因为我们现在只搜索该表的一小部分 - 即它不应该执行全表扫描,因为正在JOINED的字段被索引。

+0

所以基本上你试图迫使它通过EMAILID了'消息LIKE'之前做的过滤器%word%''有点发生?或者这正是你想要阻止的事情? – 2010-06-23 12:37:08

+0

是的,那正是我想要发生的事情。可能它正在发生,mySQL在取得这些结果(33,000)时会很慢,然后搜索它们。但似乎奇怪的是,在索引列中找到ticket_subject表中的33,000个结果要比在非索引列上在该表中搜索300,000个结果要慢。 – 2010-06-23 23:09:10

回答

8

记住带布尔short-circuit evaluation的优势:

SELECT COUNT(*) 
FROM messages 
join emails ON emails.id = messages.emailid 
WHERE ownership = 32 AND message LIKE '%word%' 

该过滤器由ownership则计算LIKE谓语前。总是把你的便宜的表情放在左边。

此外,我同意@Martin Smith和@MJB,您应该考虑使用MySQL的FULLTEXT索引来加快速度。


回复您的评论和其它信息,这里的一些分析:

explain SELECT COUNT(*) FROM ticket WHERE category IN (1)\G 

      id: 1 
    select_type: SIMPLE 
     table: ticket 
     type: ref 
possible_keys: category 
      key: category 
     key_len: 4 
      ref: const 
     rows: 1 
     Extra: Using index 

的说明“使用索引”是一件好事,看看,因为这意味着它能够满足查询只需读取索引数据结构,甚至不涉及表格的数据。这肯定会跑得非常快。

explain SELECT COUNT(*) FROM ticket_subject WHERE subject LIKE '%about%'\G 

      id: 1 
    select_type: SIMPLE 
     table: ticket_subject 
     type: ALL 
possible_keys: NULL  <---- no possible keys 
      key: NULL 
     key_len: NULL 
      ref: NULL 
     rows: 1 
     Extra: Using where 

这表明没有可能有益于通配符LIKE谓词的可能键。它使用WHERE子句中的条件,但必须通过运行表扫描来评估它。

explain SELECT COUNT(*) FROM ticket LEFT JOIN ticket_subject 
ON (ticket_subject.ticketid = ticket.id) 
WHERE category IN (1) 
AND ticket_subject.subject LIKE '%about%'\G 

      id: 1 
    select_type: SIMPLE 
     table: ticket 
     type: ref 
possible_keys: PRIMARY,category 
      key: category 
     key_len: 4 
      ref: const 
     rows: 1 
     Extra: Using index 

      id: 1 
    select_type: SIMPLE 
     table: ticket_subject 
     type: ref 
possible_keys: ticketid 
      key: ticketid 
     key_len: 4 
      ref: test.ticket.id 
     rows: 1 
     Extra: Using where 

同样,访问票表快,但是这由LIKE状态所发生的表扫描宠坏了。

ALTER TABLE ticket_subject ENGINE=MyISAM; 

CREATE FULLTEXT INDEX ticket_subject_fulltext ON ticket_subject(subject); 

explain SELECT COUNT(*) FROM ticket JOIN ticket_subject 
ON (ticket_subject.ticketid = ticket.id) 
WHERE category IN (1) AND MATCH(ticket_subject.subject) AGAINST('about') 

      id: 1 
    select_type: SIMPLE 
     table: ticket 
     type: ref 
possible_keys: PRIMARY,category 
      key: category 
     key_len: 4 
      ref: const 
     rows: 1 
     Extra: Using index 

      id: 1 
    select_type: SIMPLE 
     table: ticket_subject 
     type: fulltext 
possible_keys: ticketid,ticket_subject_fulltext 
      key: ticket_subject_fulltext   <---- now it uses an index 
     key_len: 0 
      ref: 
     rows: 1 
     Extra: Using where 

你永远不会做出LIKE表现良好。看到我的介绍Practical Full-Text Search in MySQL


回复您的评论:好吧,我已经做了类似规模的数据集一些实验(在用户和徽章在堆栈溢出数据表转储:-)。这里是我发现的:

select count(*) from users 
where reputation > 50000 

+----------+ 
| count(*) | 
+----------+ 
|  37 | 
+----------+ 
1 row in set (0.00 sec) 

这真的很快,因为我在声望列上有一个索引。

  id: 1 
    select_type: SIMPLE 
     table: users 
     type: range 
possible_keys: users_reputation_userid_displayname 
      key: users_reputation_userid_displayname 
     key_len: 4 
      ref: NULL 
     rows: 37 
     Extra: Using where; Using index 

select count(*) from badges 
where badges.creationdate like '%06-24%' 

+----------+ 
| count(*) | 
+----------+ 
|  1319 | 
+----------+ 
1 row in set, 1 warning (0.63 sec) 

这是预料之中的,因为该表有700k行,它必须执行表扫描。现在让我们来加入:

select count(*) from users join badges using (userid) 
where users.reputation > 50000 and badges.creationdate like '%06-24%' 

+----------+ 
| count(*) | 
+----------+ 
|  19 | 
+----------+ 
1 row in set, 1 warning (0.03 sec) 

这似乎并不坏。这里的解释报告:

  id: 1 
    select_type: SIMPLE 
     table: users 
     type: range 
possible_keys: PRIMARY,users_reputation_userid_displayname 
      key: users_reputation_userid_displayname 
     key_len: 4 
      ref: NULL 
     rows: 37 
     Extra: Using where; Using index 

      id: 1 
    select_type: SIMPLE 
     table: badges 
     type: ref 
possible_keys: badges_userid 
      key: badges_userid 
     key_len: 8 
      ref: testpattern.users.UserId 
     rows: 1 
     Extra: Using where 

这似乎像它的使用索引智能化的加入,它可以帮助我有包括用户ID和美誉度一个复合索引。请记住,MySQL只能为每个表使用一个索引,因此为需要执行的查询定义正确的复合索引非常重要。


回复您的评论:OK,我已经试过这其中口碑> 5000,并在信誉> 500,并在信誉> 50.这些应该匹配更大的一组用户。

select count(*) from users join badges using (userid) 
where users.reputation > 5000 and badges.creationdate like '%06-24%' 

+----------+ 
| count(*) | 
+----------+ 
|  194 | 
+----------+ 
1 row in set, 1 warning (0.27 sec) 

select count(*) from users join badges using (userid) 
where users.reputation > 500 and badges.creationdate like '%06-24%' 

+----------+ 
| count(*) | 
+----------+ 
|  624 | 
+----------+ 
1 row in set, 1 warning (0.93 sec) 

select count(*) from users join badges using (userid) 
where users.reputation > 50 and badges.creationdate like '%06-24%' 
-------------- 

+----------+ 
| count(*) | 
+----------+ 
|  1067 | 
+----------+ 
1 row in set, 1 warning (1.72 sec) 

的解释报告是在所有情况下是相同的,但如果查询的用户表中寻找更多的匹配行,那么它自然要评估对在徽章表了很多更多的匹配行的LIKE谓语。

这确实是有一些成本做一个加入。有点惊人的是,它非常昂贵。但是如果你使用索引,这可以被缓解。

我知道你说你不能使用索引的查询,但也许是时候考虑创建您的原始列的数据的一些变换形式冗余列,所以你可以指数吧。在上面的示例中,我可能会创建一个列creationdate_day并从DAYOFYEAR(creationdate)填充它。


这里就是我的意思是:

ALTER TABLE Badges ADD COLUMN creationdate_day SMALLINT; 
UPDATE Badges SET creationdate_day = DAYOFYEAR(creationdate); 
CREATE INDEX badge_creationdate_day ON Badges(creationdate_day); 

select count(*) from users join badges using (userid) 
where users.reputation > 50 and badges.creationdate_day = dayofyear('2010-06-24') 

+----------+ 
| count(*) | 
+----------+ 
|  1067 | 
+----------+ 
1 row in set, 1 warning (0.01 sec) <---- not too shabby! 

这里的解释报告:

  id: 1 
    select_type: SIMPLE 
     table: badges 
     type: ref 
possible_keys: badges_userid,badge_creationdate_day 
      key: badge_creationdate_day <---- here is our new index 
     key_len: 3 
      ref: const 
     rows: 1318 
     Extra: Using where 

      id: 1 
    select_type: SIMPLE 
     table: users 
     type: eq_ref 
possible_keys: PRIMARY,users_reputation_userid_displayname 
      key: PRIMARY 
     key_len: 8 
      ref: testpattern.badges.UserId 
     rows: 1 
     Extra: Using where 
+0

+1从来没有想过 – DrColossos 2010-06-23 12:50:17

+1

我不熟悉MySQL,但是你确定它在创建执行计划时没有对where谓词重新排序吗? – Mike 2010-06-23 12:53:54

+0

@Mike:是的,我确定。没有支持短路评估的编程语言应该重新排列布尔表达式! – 2010-06-23 12:59:30

3
SELECT COUNT(*) 
FROM messages 
join emails ON emails.id = messages.emailid 
WHERE message LIKE '%word%' 
AND ownership = 32 

问题虽然是与'%word%'这总是需要扫描的消息。如果您使用的是MyISAM,则可能需要查看full text search

+0

我真的想说明一个快速搜索结果与慢速搜索结果的结合情况。 但是在这种情况下,%word%上的搜索应该非常快,因为它只搜索索引选定的几百或几千行。 – 2010-06-23 13:25:00

+0

@Chris - 你可以用你迄今为止尝试过的最好的问题来更新你的问题,它是解释计划吗? – 2010-06-23 13:47:49

+0

刚刚完成。 – 2010-06-23 23:06:20

2

我认为这是你在找什么:

select count(*) 
from messages m 
    inner join emails e 
    on e.id = m.emailid 
where m.message like '%word%' 
    and e.ownership = 32 

很难说肯定会表现如何。如果FTS是因为WORD上的启动通配符,那么这样做并不能解决问题。但好消息是,连接可能会限制消息表中的记录,您必须查看。

+0

谢谢,这在马丁的回答中以相同的速度执行。它比运行慢速查询(针对邮件的%word%)慢3倍。 – 2010-06-23 13:30:35

+0

@Chris - 我认为有一个问题是,您正在对非索引列进行加入 - emails.id - 因此,除非您为该列编制索引,否则无法加快其速度。您也在该表上强制进行全表扫描(FTS)。 – MJB 2010-06-23 14:11:55

+0

此列已编入索引。我在上面提供了一个EXPLAIN。 – 2010-06-23 23:06:52

0

您是否可以通过其他方式转接连接?看起来第二个查询是一个比较便宜的查询,并且由于整个事情只是一个简单的连接,因此您希望执行更便宜的查询来缩小数据集的范围,然后对更昂贵的查询进行连接。

+0

那么子查询会很快我相信因为它会使用索引来获取它需要检查的消息列表,然后只会处理这些消息。问题是我无法创建一个似乎适用于该逻辑的连接;我所有的连接速度比对整个表运行昂贵的查询要慢3倍。 – 2010-06-23 13:31:48