2014-03-18 47 views
0

我有非常大的表,其中17,044,833行和6.4 GB的大小。我正在运行下面的简单查询,它需要5秒钟。任何想法可以做什么优化来提高查询的速度?MySQL:大数据缓慢读取

SELECT 
`stat_date`, 
SUM(`adserver_impr`), 
SUM(`adserver_clicks`) 
FROM `dfp_stats` WHERE 
`stat_date` >= '2014-02-01' 
AND 
`stat_date` <= '2014-02-28' 

MySQL的配置:

key_buffer    = 16M 
max_allowed_packet  = 16M 
thread_stack   = 192K 
thread_cache_size  = 8 
innodb_buffer_pool_size = 10G 

服务器:

Memory: 48GB  
Disk: 480GB 

UPDATE

原始查询:

EXPLAIN 
SELECT 
DS.`stat_date` 'DATE', 
DC.`name` COUNTRY, 
DA.`name` ADVERTISER, 
DOX.`id` ORDID, 
DOX.`name` ORDNAME, 
DLI.`id` LIID, 
DLI.`name` LINAME, 
DLI.`is_ron` ISRON, 
DOX.`is_direct` ISDIRECT, 
DSZ.`size` LISIZE, 
PUBSITE.`id` SITEID, 

SUM(DS.`adserver_impr`) 'DFPIMPR', 
SUM(DS.`adserver_clicks`) 'DFPCLCKS', 
SUM(DS.`adserver_rev`) 'DFPREV' 

FROM `dfp_stats` DS 
LEFT JOIN `dfp_adunit1` AD1 ON AD1.`id` = DS.`dfp_adunit1_id` 
LEFT JOIN `dfp_adunit2` AD2 ON AD2.`id` = DS.`dfp_adunit2_id` 
LEFT JOIN `dfp_adunit3` AD3 ON AD3.`id` = DS.`dfp_adunit3_id` 
LEFT JOIN `dfp_orders` DOX ON DOX.`id` = DS.`dfp_order_id` 
LEFT JOIN `dfp_advertisers` DA ON DA.`id` = DOX.`dfp_advertiser_id` 
LEFT JOIN `dfp_lineitems` DLI ON DLI.`id` = DS.`dfp_lineitem_id` 
LEFT JOIN `dfp_countries` DC ON DC.`id` = DS.`dfp_country_id` 
LEFT JOIN `dfp_creativesize` DSZ ON DSZ.`id` = DS.`dfp_creativesize_id` 
LEFT JOIN `pubsites` PUBSITE 
ON AD1.`pubsite_id` = PUBSITE.`id` 
OR AD2.`pubsite_id` = PUBSITE.`id` 

WHERE 
DS.`stat_date` >= '2014-02-01' 
AND DS.`stat_date` <= '2014-02-28' 
AND PUBSITE.`id` = 6 
GROUP BY DLI.`id`,DS.`stat_date`; 

的结果可以解释:(这是将覆盖索引之后)

http://i.stack.imgur.com/vhVeB.png

回答

1

对于此查询的最佳性能,打造一个覆盖索引:

... ON `dfp_stats` (`stat_date`,`adserver_impr`,`adserver_clicks`) 

从EXPLAIN应该显示输出“使用索引“。这意味着查询可以完全从索引中满足,而无需访问基础表中的任何页面。 (术语“覆盖索引”是指包括查询引用的所有列的索引)。

至少,您需要一个具有前导列的索引stat_date,以便查询可以使用索引范围扫描操作。索引范围扫描本质上可以跳过行装载,并更快地找到实际需要检查的行。

只要更改MySQL实例的配置,这实际上取决于表是InnoDB还是MyISAM。


随访

对于InnoDB,内存仍然是王道。如果服务器上有可用内存,则可以增加innodb_buffer_pool。

还考虑启用MySQL查询缓存。 (我们只对查询启用查询缓存,该查询专门用于使用SQL_CACHE关键字(即SELECT SQL_CACHE t.foo,)的缓存,所以我们不会使用不会给我们带来好处的查询混淆缓存。对于其他查询,我们避免运行额外的代码(否则将需要)来搜索缓存并保持缓存内容

我们从查询缓存中获益的地方是来自“昂贵的”查询(查看大量的行并查看针对相对静态的表执行大量连接),并返回小的结果集。(我认为得到一个单列从行的整个一大堆一和的查询将是查询缓存的一个很好的候选人,如果表很少更新,或者如果相同的查询将被多次运行之前在表上DML操作无效的高速缓存)。


这是一个有点奇怪,你的查询返回一个非集合,是不是在GROUP BY子句。

如果您的查询使用的是stat_date上的索引,则可能查询返回谓词指定的范围内的最小值stat_date;所以很可能您会使用SELECT MIN(stat_date) AS stat_date获得同等结果。


一个更复杂的方法是建立一个“汇总”表,并刷新定期与来自查询的结果,然后让应用程序查询汇总表。 (数据仓库类型的方法。)如果您需要“最新的”准确性,这不起作用。为此,您可能需要在目标表上引入触发器,以便在INSERT,UPDATE和DELETE操作上维护汇总表。

如果我去了这条道路,我可能会选择存储摘要行各stat_date,所以它可以容纳查询的任何范围或设定日期...

CREATE TABLE dfp_stats_summary 
(stat_date  DATE NOT NULL PRIMARY KEY 
, adserver_impr BIGINT 
, adserver_clicks BIGINT 
) ENGINE=InnoDB ; 

-- refresh 
INSERT INTO dfp_stats_summary (stat_date, adserver_impr, adserver_clicks) 
SELECT t.stat_date 
    , SUM(t.adserver_impr) AS adserver_impr 
    , SUM(t.adserver_clicks) AS adserver_clicks 
    FROM dfp_stats 
GROUP BY t.stat_date 
    ON DUPLICATE KEY 
     UPDATE adserver_impr = VALUES(adserver_impr) 
      , adserver_clicks = VALUES(adserver_clicks) 
; 

刷新查询将曲柄;您可能希望在WHERE子句中指定一个日期范围,以一次执行一两个月,并循环所有可能的月份。

随着人口汇总表,只是改变了原来的查询引用新的汇总表,而不是详细信息表。总计28个汇总行比数十万个详细行要快得多。


+0

我会尝试创建一个覆盖索引。我已经有一个关于stat_date的索引。这些表都是InnoDB。 – salmandem

+0

@ iser2884319:注意:如果创建一个以stat_date为前导列的多列索引,则不需要仅在单个“stat_date”列上的其他索引(除非它用于强制执行唯一约束) – spencer7593

+0

所以基本上你说的是尝试EXPLAIN来说使用索引?因此,如果我在查询中添加更多列,我将不得不将其包含在我的覆盖索引中,对吗? – salmandem