如何改善此表的索引

时间:2015-08-27 19:32:03

标签: mysql indexing

我有这个查询

EXPLAIN SELECT 
  GA,
  mkt_cd,
  mkt_name 
FROM
  (SELECT 
    SUM(GA) AS GA,
    sales_data_mkt.mkt_cd,
    sales_data_mkt.mkt_name 
  FROM
    sales_data_mkt 
  WHERE sales_data_mkt.country_cd = 'USA' 
    AND activity_dt BETWEEN '2015-08-01' 
    AND '2015-08-31' 
    AND sales_data_mkt.mkt_cd IS NOT NULL 
  GROUP BY sales_data_mkt.mkt_cd 
  ORDER BY (SUM(GA)) DESC) AS innerQuery 
WHERE GA > 0 
LIMIT 10 

我有一个索引 country_cd, activity_dt, mkt_cd

explain语句返回:

Key used: country_cd,activity_dt,mkt_cd

key_len: 12 ref: const

rows: 909518

Extra: 使用索引条件;用在哪里;使用临时;使用filesort

此查询大约需要5秒才能返回包含200万行的表。从我过去的阅读Using temporary and Using filesort来看,表现不佳。我该如何微调这个查询呢?

这是创建声明

CREATE TABLE `sales_data_mkt` (
  `ACTIVITY_DT` date DEFAULT NULL,
  `Country_Cd` varchar(3) DEFAULT NULL,
  `AREA_CD` char(2) DEFAULT NULL,
  `AREA_DESC` varchar(30) DEFAULT NULL,
  `REGION_CD` char(2) DEFAULT NULL,
  `REGION_DESC` varchar(30) DEFAULT NULL,
  `MKT_CD` char(4) DEFAULT NULL,
  `MKT_NAME` varchar(30) DEFAULT NULL,
  `device_tier` varchar(32) DEFAULT NULL,
  `SLS_DIST_CHNL_TYPE_CD` char(3) DEFAULT NULL,
  `PPlan_Type` varchar(14) DEFAULT NULL,
  `PREPAID_IND` char(1) DEFAULT NULL,
  `edge_taken_ind` varchar(1) DEFAULT NULL,
  `Edge_Desc` varchar(16) DEFAULT NULL,
  `Data_Plan_Tier` varchar(26) DEFAULT NULL,
  `Unlimited_to_Others_cnt` int(11) DEFAULT NULL,
  `Data_Step_UP_Cnt` int(11) DEFAULT NULL,
  `Data_Step_Down_Cnt` int(11) DEFAULT NULL,
  `lines` int(11) DEFAULT NULL,
  `GA` int(11) DEFAULT NULL,
  `DE` int(11) DEFAULT NULL,
  `NetAdd` int(11) DEFAULT NULL,
  `VOL_DE` int(11) DEFAULT NULL,
  `INVOL_DE` int(11) DEFAULT NULL,
  `PortIn_ATT_Leap` int(11) DEFAULT NULL,
  `PortIn_Sprint_Nextel` int(11) DEFAULT NULL,
  `PortIn_TMobile_MetroPcs` int(11) DEFAULT NULL,
  `PortIn_OtherCarriers` int(11) DEFAULT NULL,
  `PortOut_ATT_Leap` int(11) DEFAULT NULL,
  `PortOut_Sprint_Nextel` int(11) DEFAULT NULL,
  `PortOut_TMobile_MetroPcs` int(11) DEFAULT NULL,
  `PortOut_OtherCarriers` int(11) DEFAULT NULL,
  `Edge_Net_Sales` int(11) DEFAULT NULL,
  `Edge_Eligible_Net_Sales` int(11) DEFAULT NULL,
  `Edge_Net_Sales_All` int(11) DEFAULT NULL,
  `Basic_To_Smart` int(11) DEFAULT NULL,
  `AAL` int(11) DEFAULT NULL,
  `New_To_VZ` int(11) DEFAULT NULL,
  `Trade_In` int(11) DEFAULT NULL,
  `Unlimited_to_Others` int(11) DEFAULT NULL,
  `Data_Step_Up` int(11) DEFAULT NULL,
  `Data_Step_Down` int(11) DEFAULT NULL,
  KEY `MKT_CD` (`MKT_CD`,`ACTIVITY_DT`),
  KEY `REGION_CD` (`REGION_CD`,`ACTIVITY_DT`,`MKT_CD`),
  KEY `AREA_CD` (`AREA_CD`,`ACTIVITY_DT`),
  KEY `Country_Cd` (`Country_Cd`,`ACTIVITY_DT`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

2 个答案:

答案 0 :(得分:0)

您不需要子查询,此版本有帮助吗?

SELECT 
    SUM(GA) AS TotalGA,
    sales_data_mkt.mkt_cd,
    sales_data_mkt.mkt_name 
  FROM
    sales_data_mkt 
  WHERE sales_data_mkt.country_cd = 'USA' 
    AND activity_dt BETWEEN '2015-08-01' AND '2015-08-31' 
    AND sales_data_mkt.mkt_cd IS NOT NULL 
  GROUP BY sales_data_mkt.mkt_cd 
  HAVING TotalGA > 0 
  ORDER BY TotalGA DESC
  LIMIT 10 
  ;

即使没有将GA重命名为TotalGA,它也可能有用,这看起来很错误,对我来说可能有问题。

旁注:如果activity_dt是DATETIME或TIMESTAMP,那么BETWEEN可能无法完全按预期工作。

答案 1 :(得分:0)

构建"复合材料" index:首先放置= constant列,然后 一个'范围'列。

  WHERE country_cd = 'USA' 
    AND activity_dt BETWEEN '2015-08-01' AND '2015-08-31' 
    AND mkt_cd IS NOT NULL 

所以,这需要以下任何一个:

INDEX(country_cd, mkt_cd, activity_dt)
INDEX(mkt_cd, country_cd, activity_dt)

它说Using index表示查询完全在索引中完成。

key_len: 12 ref: const

需要一些解码。它表示 const ,而不是const,const,所以它只使用了1个字段。它使用activity_dt进行范围检查(不在12中包含它,但它必须偶然发现mkt_cd的错误值,使其变慢。

12来自 Country_Cd varchar(3) DEFAULT NULL 。但你问怎么样? key_len是最大值,因此有3个字符。因为它是VAR,所以需要2个字节的长度。 NULL的另一个字节。由于utf8,每个字符都是3个字节。所以,3 * 3 + 2 + 1 = 12个字节。

  • 提示:如果列始终具有值,请说出NOT NULL
  • 提示:在始终为ascii的列上明确说出CHARACTER SET ascii,例如"国家/地区代码"。
  • 提示:由于国家/地区代码标准化为3个字母,因此不需要VAR。 (我假设您使用的是3个字母的标准,而不是2个字母的标准。)

按照提示操作,key_len将从12降至1 * 3 + 0 + 0 = 3个字节。

  • 提示:使用不带LIMIT的{​​{1}}可让优化程序为您提供感觉到的任何行。只需看几行就可以了,但对于Production来说这是危险的不可预测的。 (不,优化器可以自由忽略内部ORDER BY。)
  • 提示:使用ORDER BY,您不需要子查询(如Uueerdo所说):

    HAVING

  • 提示:InnoDB确实需要SELECT SUM(GA) AS GA, mkt_cd, mkt_name FROM sales_data_mkt WHERE country_cd = 'USA' AND activity_dt BETWEEN '2015-08-01' AND '2015-08-31' AND mkt_cd IS NOT NULL GROUP BY mkt_cd HAVING SUM(GA) > 0 ORDER BY SUM(GA) DESC LIMIT 10 ;。如果您没有PRIMARY KEY的列组合,请添加UNIQUE

  • 提示:为避免计算月末,闰年和缺少整天,请将INT UNSIGNED NOT NULL AUTO_INCREMENT更改为

    activity_dt BETWEEN '2015-08-01' AND '2015-08-31'
    activity_dt >= '2015-08-01' AND

这对于activity_dt < '2015-08-01' + INTERVAL 1 MONTHDATEDATETIME,甚至是新的微秒精度数据类型都能正常工作。