记得利用布尔值short-circuit evaluation:
SELECT COUNT(*)
FROM messages
join emails ON emails.id = messages.emailid
WHERE ownership = 32 AND message LIKE '%word%'
这会在评估 LIKE 谓词之前按 ownership 过滤。总是把你便宜的表达放在左边。
另外,我同意 @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 每个表只能使用一个索引,因此为您需要执行的查询定义正确的复合索引非常重要。
您的评论:好的,我已经尝试过声誉 > 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)
解释报告在所有情况下都是相同的,但如果查询在 Users 表中找到更多匹配行,那么它自然必须根据 Badges 表中更多匹配行来评估 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