大型MySQL表,选择速度很慢

时间:2011-08-17 10:20:40

标签: mysql

我在MySQL中有一个大表(在MAMP中运行),它有2800万行,大小为3.1GB。这是它的结构

    CREATE TABLE `termusage` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `termid` bigint(20) DEFAULT NULL,
  `date` datetime DEFAULT NULL,
  `dest` varchar(255) DEFAULT NULL,
  `cost_type` tinyint(4) DEFAULT NULL,
  `cost` decimal(10,3) DEFAULT NULL,
  `gprsup` bigint(20) DEFAULT NULL,
  `gprsdown` bigint(20) DEFAULT NULL,
  `duration` time DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `termid_idx` (`termid`),
  KEY `date_idx` (`date`),
  KEY `cost_type_idx` (`cost_type`),
  CONSTRAINT `termusage_cost_type_cost_type_cost_code` FOREIGN KEY (`cost_type`) REFERENCES `cost_type` (`cost_code`),
  CONSTRAINT `termusage_termid_terminal_id` FOREIGN KEY (`termid`) REFERENCES `terminal` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28680315 DEFAULT CHARSET=latin1

以下是SHOW TABLE STATUS的输出:

Name,Engine,Version,Row_format,Rows,Avg_row_length,Data_length,Max_data_length,Index_length,Data_free,Auto_increment,Create_time,Update_time,Check_time,Collation,Checksum,Create_options,Comment    
'termusage', 'InnoDB', '10', 'Compact', '29656469', '87', '2605711360', '0', '2156920832', '545259520', '28680315', '2011-08-16 15:16:08', NULL, NULL, 'latin1_swedish_ci', NULL, '', ''

我试图运行以下select语句:

    select u.id from termusage u
    where u.date between '2010-11-01' and '2010-12-01'

返回结果需要35分钟(大约1400万行) - 这是使用MySQL Worksbench。

我有以下MySQL配置设置:

Variable_name              Value
bulk_insert_buffer_size    8388608
innodb_buffer_pool_instances   1
innodb_buffer_pool_size    3221225472
innodb_change_buffering    all
innodb_log_buffer_size     8388608
join_buffer_size               131072
key_buffer_size            8388608
myisam_sort_buffer_size    8388608
net_buffer_length              16384
preload_buffer_size            32768
read_buffer_size               131072
read_rnd_buffer_size       262144
sort_buffer_size               2097152
sql_buffer_result              OFF

最终我试图运行一个更大的查询 - 连接几个表并组合一些数据,所有这些都基于变量 - 客户ID -

select c.id,u.termid,u.cost_type,count(*) as count,sum(u.cost) as cost,(sum(u.gprsup) + sum(u.gprsdown)) as gprsuse,sum(time_to_sec(u.duration)) as duration 
from customer c
inner join terminal t
on (c.id = t.customer)
inner join termusage u
on (t.id = u.termid)
where c.id = 1 and u.date between '2011-03-01' and '2011-04-01' group by c.id,u.termid,u.cost_type

这最多返回8行(因为只有8个单独的cost_types - 但是这个查询在termusage表中要计算的行数不多(少于1百万)时运行正常 - 但是当数量为termusage表中的行很大 - 如何缩短选择时间。

使用LOAD DATA方法每月从CSV文件中将数据添加到termusage表中一次 - 因此不需要对插入进行非常精确的调整。

编辑:显示主要查询的解释:

id,select_type,table,type,possible_keys,key,key_len,ref,rows,Extra
1,SIMPLE,c,const,PRIMARY,PRIMARY,8,const,1,"Using index; Using temporary; Using filesort"
1,SIMPLE,u,ALL,"termid_idx,date_idx",NULL,NULL,NULL,29656469,"Using where"
1,SIMPLE,t,eq_ref,"PRIMARY,customer_idx",PRIMARY,8,wlnew.u.termid,1,"Using where"

3 个答案:

答案 0 :(得分:3)

看起来你问了两个问题 - 对吗?

第一个查询花费这么长时间的最可能原因是因为它受IO限制。将140万条记录从磁盘传输到MySQL工作台需要很长时间。

您是否尝试通过“解释”提出第二个查询?是的,你只能获得8行 - 但SUM操作可能是数百万条记录的总和。

我假设“客户”和“终端”表格已正确编入索引?当你加入关于termusage的主键时,那应该非常快......

答案 1 :(得分:0)

您可以尝试删除按日期限制的where子句,而是在select中放置一个IF语句,这样如果日期在这些边界内,则返回该值,否则返回零值。然后,SUM当然只对这个范围内的值求和,因为所有其他值都为零。

获取比您需要的更多行听起来有点荒谬但我们最近在Oracle数据库上观察到这取得了相当大的改进。当然,这将取决于许多其他因素,但它可能值得一试。

答案 2 :(得分:0)

您也可以考虑将表分解为数年或数月。所以你有一个termusage_2010,termusage_2011,......或类似的东西。

不是一个非常好的解决方案,但是看到你的桌子非常大,它可能在较小的服务器上很有用。