两张桌子。
电子邮件 id(int10)|所有权(int10)
消息 emailid(int10)已编入索引消息(中等文本)
子查询(在mysql中很糟糕)。
SELECT COUNT(*)FROM messages WHERE消息LIKE'%word%'AND emailid IN(SELECT id FROM emails WHERE ownership = 32)
这里的用法是我对电子邮件进行搜索(在上面的示例中显然已经简化了),它会生成一个包含3,000个电子邮件ID的列表。然后,我想对邮件进行搜索,因为我需要进行文本匹配 - 仅针对该邮件的3000封电子邮件。
对邮件的查询很昂贵(邮件没有编入索引),但这很好,因为它只会检查几行。
思路:
i)加入。到目前为止,我对此的尝试都没有奏效,导致对消息表进行全表扫描(即未使用的emailid索引) ii)临时表。我认为这可行。 iii)缓存客户端中的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秒并返回113的计数(*)。
运行:
SELECT COUNT (*) FROM ticket WHERE category IN (1)
需要0.01秒才能找到33,000个结果。
运行
SELECT COUNT (*) FROM ticket_subject WHERE subject LIKE "%about%"
花费0.14秒并找到1,300个结果。
票证表和ticket_subject表都有300,000行。
ticket_subject.ticketid和ticket.category上有一个索引。
我现在意识到使用LIKE语法是一个错误 - 因为它有点像FULLTEXT的红色鲱鱼。这不是问题。问题是:
1)表A - 非常快的查询,在索引上运行。 0.001秒 2)表B - 中等到慢的查询,没有索引 - 进行全表扫描。 0.1秒。
这两个结果都很好。问题是我必须加入它们,搜索需要0.3秒;这对我来说没有意义,因为表B上的组合查询的缓慢方面应该更快,因为我们现在只搜索该表的一小部分 - 即它不应该进行全表扫描,因为正在加入的字段在索引上。
答案 0 :(得分: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。
重新评论:好的,我已经对类似大小的数据集(Stack Overflow数据转储中的用户和徽章表)进行了一些实验:-)。这是我发现的:
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每个表只能使用一个索引,因此为您需要的查询定义正确的复合索引非常重要。
重新评论:好的,我已经尝试了这个名声&gt; 5000,和声誉&gt; 500,和声誉&gt; 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)
解释报告在所有情况下都是相同的,但如果查询在Users表中找到更多匹配的行,那么它自然必须针对徽章表中的更多匹配行评估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
答案 1 :(得分:3)
SELECT COUNT(*)
FROM messages
join emails ON emails.id = messages.emailid
WHERE message LIKE '%word%'
AND ownership = 32
问题在于'%word%'
这总是需要扫描消息。如果您使用MyISAM
,则可能需要查看full text search。
答案 2 :(得分: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上的起始通配符,那么这样做就不能解决问题。但好消息是,加入可能会限制您必须查看的消息表中的记录。
答案 3 :(得分:0)
你是否有可能以其他方式转变联盟?似乎第二个查询是一个较便宜的查询,因为整个事情是一个简单的连接,那么你想要执行较便宜的查询来缩小数据集的范围,然后连接到更昂贵的查询。