MySQL:大数据慢读

时间:2014-03-18 19:10:28

标签: mysql

我有非常大的表,行数为17,044,833,大小为6.4 GB。我正在运行下面的简单查询,需要5秒钟。任何想法我可以做些什么来提高这个查询的速度?

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

更新

ORIGINAL QUERY:

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`;

解释结果:(这是在添加COVERING INDEX之后)

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

2 个答案:

答案 0 :(得分:1)

如果您还没有,您可能希望将stat_date字段编入索引,以便更快地进行查找。这是语法:

ALTER TABLE TABLE_NAME ADD INDEX (COLUMN_NAME);

在此处阅读有关索引和优化的详情:https://dev.mysql.com/doc/refman/5.5/en/optimization-indexes.html

答案 1 :(得分:1)

为了获得此查询的最佳性能,请创建覆盖索引:

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

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

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

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


<强>后续

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

还考虑启用MySQL查询缓存。 (我们只为专门启用了使用SQL_CACHE关键字SELECT SQL_CACHE t.foo,的缓存的查询启用了查询缓存,因此我们不会通过不能提供给我们的查询来混淆缓存对于其他查询,我们避免运行额外的代码(否则需要)来搜索缓存并维护缓存内容。

我们从查询缓存中获益的地方来自&#34;昂贵的&#34;查询(查看大量行并执行大量连接)对应相对静态的表,并返回小的结果集。 (我认为如果表不经常更新,或者如果要运行相同的查询,那么从一整行行中获取带有SUM的单行的查询将是查询缓存的良好候选者在表上的DML操作之前多次使缓存无效。)


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

如果您的查询在stat_date上使用索引,则查询可能会在谓词指定的范围内返回stat_date的最低值;因此,您可能会使用SELECT MIN(stat_date) AS stat_date获得相同的结果。


更复杂的方法是设置&#34;摘要&#34;表,并定期刷新查询结果,然后让应用程序查询摘要表。 (数据仓库类型方法。)如果您需要&#34;最新的&#34;这不起作用。准确性。为此,您可能需要在目标表上引入触发器,以维护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个摘要行比数十万个细节行要快得多。