2009-02-14 29 views
0

最近,在缓存到memcache之前,我的查询一直在处理中!在这个例子中,花了10秒。我所要做的就是在这种情况下获得10个最近的点击。MySQL性能

我感觉到它加载了所有125,592行然后只返回10,对不对?

# [email protected]: root[root] @ localhost [] 
# Query_time: 10 Lock_time: 0 Rows_sent: 10 Rows_examined: 125592 
SELECT * FROM hits WHERE campaign_id = 30 ORDER BY id DESC LIMIT 10;

这里是另外一个慢查询:

 
# Time: 090214 5:00:40 
# [email protected]: root[root] @ localhost [] 
# Query_time: 3 Lock_time: 0 Rows_sent: 1 Rows_examined: 128879 
SELECT count(DISTINCT(ip_address)) AS count_distinct_ip_address FROM `hits` WHERE (campaign_id = 30);

当运行查询phpMyAdmin的,它需要1.3395秒。虽然只做SELECT * FROM hits只需要0.0001秒。 我觉得很奇怪,返回的所有命中少于通过它们排序,还是只是,我通过它们排序?

对于那些谁希望看到我的表:

CREATE TABLE `hits` (
    `id` int(11) unsigned NOT NULL auto_increment, 
    `hostname` varchar(255) NOT NULL, 
    `url` tinytext NOT NULL, 
    `user_agent` tinytext NOT NULL, 
    `created_at` timestamp NOT NULL default CURRENT_TIMESTAMP, 
    `ip_address` varchar(15) NOT NULL, 
    `campaign_id` int(11) NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `campaign_id` (`campaign_id`), 
    KEY `ip_address` (`ip_address`) 
);
+0

你对这些查询运行解释吗? – jmucchiello 2009-02-14 05:39:34

+0

我刚刚发布相同的问题... 请运行EXPLAIN SELECT * FROM命中WHERE campaign_id = 30 ORDER BY id DESC LIMIT 10; – regex 2009-02-14 05:57:04

+0

我建议你避免使用phpMyAdmin;这不是一个有用的工具。它的行为太不可预知,并且它在命令行客户端中存在很多错误。 – MarkR 2009-02-14 23:25:06

回答

5

看来你的campaign_id指数具有低选择性,即这个值有很多记录。

订购这么多的记录需要很多时间。

尝试在PRIMARY KEY使用订货:

/* Edited, as MySQL does not use live feed from the derived source with ORDER BY */ 
SELECT * 
FROM hits 
WHERE IFNULL(campaign_id, campaing_id) = 30 
ORDER BY id DESC 
LIMIT 10; 

关于你的第二个查询,没有太多可以做,因为你需要在整个campaign_id = 30一个完整的扫描,无论如何,无论是TABLE SCAN或。

事实上,TABLE SCAN能更快:

SELECT count(DISTINCT(ip_address)) AS count_distinct_ip_address 
FROM `hits` 
WHERE IFNULL(campaign_id, campaign_id) = 30; 

如果不是,你可以在(campaign_id, ip_address)创建索引,并用一招,这一指数模仿INDEX GROUP BY

CREATE INDEX ix_hits_campaign_ip ON hits(campaign_id, ip_address) 

SELECT SUM(cnt) 
FROM (
SELECT CASE WHEN @r = ip_address THEN 0 ELSE 1 END AS cnt, 
    @r := ip_address 
FROM 
    (SELECT @r:='') r, 
    (
    SELECT ip_address 
    FROM hits 
    WHERE campaign_id = 30 
    ORDER BY ip_address 
) i 
) o 

这里的诀窍很简单:我们不需要结果,只需要一个计数,因此不需要扫描实际值。索引扫描就足够了。

不幸的是,尽管MySQL文档在散列索引扫描上说了here,但它们实际上并不适用于组合索引。这就是为什么我们需要模仿INDEX SCAN WITH GROUP BY

我们这样做是通过强制MySQL使用INDEX RANGE SCAN来检索所有记录与campaign_id = 30排序方式ip_address。然后,我们使用会话变量@r将第一个子查询中的空字符串初始化为DISTINCT ip_address

在前面的ip_address(存储在变量中)等于当前值时,我们将变量设置为0;否则我们将其设置为1。在第二个字段中,我们将当前值ip_address分配给变量。

最后我们检索SUM在第一个字段,当然会给我们COUNT (DISTINCT ip_address)

2

(campaign_id,id)索引应该照顾第一的相当好。但不同的是有点棘手......

编辑: MySQL不会对一个查询使用多个索引;所以是的,你需要一个索引涵盖查询中涉及的所有字段。

+0

虽然我已经这样做了,请参阅主键和campaign_id的关键字? – Garrett 2009-02-14 05:19:44

1

如果查询花费很长时间来处理它,通常是因为缺少索引,磁盘IO差或其他瓶颈。一张有120 000行的表格不是很多数据的地狱,查询真的不需要那么长时间。我真的会检查磁盘io。

上面的答案1是加快查询1的一种方法。要加快查询2,您可能需要创建一个聚合表,每次点击更新或每晚更新一次,然后您可以添加在尚未汇总的日子里。日期暴怒的指数应该使这一点相对较快。

您还应该针对您的查询运行“explain”,并查看它正在使用的索引(如果有)。什么存储enigne你用于MySQL?这也会产生影响。如果您正在使用MYISAM存储引擎并且同时进行插入和读取操作,则可能会有很大的性能下降。

确保通过定期对较大的表运行“分析”来更新表格统计信息。这有助于查询引擎选择最佳查询计划。

0

只是一个猜测。

SELECT * FROM hits WHERE (campaign_id = 30 AND id > 0) ORDER BY id DESC LIMIT 10; 

希望MySQL能合并索引。祝你好运。

1

您需要使用EXPLAIN来了解它是如何执行您的查询的。你需要在生产或生产类数据上做,但显然不应该在生产系统上做(当然,你需要在开发和生产中使用相同的软件) - 上述情况表明它正在做全表扫描;这很可能是因为没有任何索引可以使用,或者选择不使用它们,因为它们的基数较低等。

然后,您需要评估可以添加哪些索引来改进它,尝试添加它们,再次测试,然后尝试通过检查添加索引不会破坏应用程序中的其他任何内容并且不会在其他位置的性能下降来对QA进行质量检查。您将需要分析空间和性能影响 - 这也可以通过测试系统上的类似生产的数据来完成(当然,性能测试需要在生产规格的硬件上完成)。

一旦您确定添加索引是正确的事情,您可以像平常一样将这些更改放入软件发行版中。尽管要注意大表上的ALTER TABLE,但它可能需要一些时间,并会阻止对表的写操作(但是,120k行可能不是一个大表)。请确保您知道需要多长时间以及它将对生产产生什么影响,然后再进行更改。