我正在构建一个Twitter应用程序,在twitter上显示已发布的链接,但在按时间对表格进行排序时遇到问题。
tweet
+----------------------------------------+
| tweet_id | [...] | created_at |
+----------------------------------------+
| 123456 | [...] | 2012-06-11 11:31:28 |
| 234567 | [...] | 2012-06-11 11:32:55 |
| 345678 | [...] | 2012-06-11 11:33:22 |
+----------------------------------------+
tweets_url
+---------------------+
| tweet_id | url |
+---------------------+
| 123456 | cnn.com |
| 123456 | fox.com |
| 234567 | abc.com |
| 345678 | abc.com |
+---------------------+
继承我的SQL(我使用GROUP by只返回唯一的URL):
SELECT tweet_urls.url,
FROM `tweets`
LEFT JOIN tweet_urls ON tweet_urls.tweet_id = tweets.tweet_id
WHERE tweet_urls.url LIKE '%cnn.com%'
GROUP BY tweet_urls.url
ORDER BY tweets.created_at DESC LIMIT 0 , 20
我尝试使用不同的连接和内部SELECTS从here的外部选择运行此查询的不同变体。
编辑:我做了一些进一步的测试。似乎Mysql基于GROUP BY tweet_urls.url创建了一个临时表,然后使用指定的索引对结果进行排序,因为它在临时表上运行。
这是EXPLAIN输出:
+----+-------------+------------+--------+---------------+---------+---------+-----+----------------------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+---------------------------------------------------------------------------------------------------------+----------------------------------------------+
| 1 | SIMPLE | tweet_urls | index | tweet_id | url | 422 | NULL 86783 | Using where; Using temporary; Using filesort
| 1 | SIMPLE | tweets | eq_ref | PRIMARY | PRIMARY | 8 | tweet_urls.tweet_id |
+----+-------------+------------+--------+---------------+---------+---------+-----+----------------------+----------------------------------------------+
答案 0 :(得分:7)
我认为真正的问题在于:
WHERE tweet_urls.url LIKE '%cnn.com%'
此类查询(LIKE
没有常量前缀)无法有效使用索引。
您可以通过向表中添加一个名为domain
的额外列并为其编制索引来解决此问题。然后,您可以将查询更改为:
WHERE tweet_urls.domain = 'cnn.com'
答案 1 :(得分:3)
在tweets.created_at
列
答案 2 :(得分:0)
在调整语句之前,请确保语句保证返回正确的结果集,即您期望的结果集。 (见下文)
至于性能,LIKE '%foo'
谓词(使用前导通配符)不可思议。 (也就是说,查询引擎无法使用索引来限制要检查的行数。查询引擎需要检查表中的每一行。
我怀疑这一点以及JOIN操作可能是导致性能下降的主要原因。 (我没有看到你的查询中需要OUTER连接,它看起来等同于INNER连接,给定了tweet_urls.url上的谓词。
理想情况下,您不需要在开头使用%通配符,而是可以检查url LIKE 'cnn.com%'
,而不使用前导通配符,该通配符有可能让查询引擎使用索引(在{上) {1}}栏)。
显然,对表和索引定义的一些更改可能有助于提高性能,但前提是您有一些自由来进行这些更改。 (通常情况下,询问像你这样的问题的海报在他们能够做出的改变方面受到限制。)
因此,我只处理您的查询,而不是建议任何架构更改。 (如果我面对像你这样的要求,我会考虑更改表和索引,甚至可能是FULLTEXT索引。)
但您询问有关更改查询的问题,因此我只会解决查询问题。
看起来好像是要返回由LATEST推文的url
排序的不同网址,而不仅仅是一条(不一定是最新的)推文的created_at
。
如果是这样,我认为您的查询不按您期望的顺序返回行,因为您在created_at
子句中引用了非ORDER BY
子句中的非聚合条款。
注意:其他关系数据库将使用这样的语句抛出异常,例如:
Oracle将抛出GROUP BY
并且SQL Server将抛出ORA-00979: not a GROUP BY expression
MySQL更自由,这并不总是一件好事。
如果您的查询按照您期望的顺序返回行,那是出于意外情况,并且不是由于某些保证行为。
要获取LATEST推文的created_at所排序的行,您需要查询不同的表单,该表单指定您要根据最大的created_at进行排序。 例如:
Msg 8127 Column "tweets.created_at" is invalid in the ORDER BY clause because it is not contained in either an aggregate function or the GROUP BY clause.
- 或
SELECT tweet_urls.url
FROM tweet_urls
WHERE tweet_urls.url LIKE '%cnn.com%'
GROUP BY tweet_urls.url
ORDER BY MAX((SELECT MAX(tweets.created_at) FROM tweets WHERE tweets.tweet_id = tweet_urls.tweet_id)) DESC LIMIT 0, 20
设置您显示的测试用例:
SELECT t.url
FROM ( SELECT tweet_urls.url, MAX(tweets.created_at) AS max_created_at
FROM tweets
JOIN tweet_urls ON tweet_urls.tweet_id = tweets.tweet_id
WHERE tweet_urls.url LIKE '%cnn.com%'
GROUP BY tweet_urls.url
) t
ORDER BY t.max_created_at DESC LIMIT 0, 20
再添加几行:
CREATE TABLE tweets (tweet_id INT UNSIGNED NOT NULL, created_at DATETIME) ENGINE=MyISAM;
CREATE TABLE tweet_urls (tweet_id INT UNSIGNED NOT NULL, url VARCHAR(20) NOT NULL) ENGINE=MyISAM;
INSERT INTO tweets VALUES (123456, '2012-06-11 11:31:28'),(234567,'2012-06-11 11:32:55'),(345678,'2012-06-11 11:33:22');
INSERT INTO tweet_urls VALUES (123456,'cnn.com'),(123456,'fox.com'),(234567,'abc.com'),(345678,'abc.com');
当我运行你的查询时,它按SOME created_at的顺序返回行,但不一定是LATEST推文的created_at。