Select 在 MySQL InnoDB Table 上查询非常慢 Table 有 160M+ 行
Select Query is very slow on MySQL InnoDB Table having 160M+ rows
下面是我创建的 table 结构和索引。 table 有 160 多万行。
create table test
(
client_id varchar(100),
user_id varchar(100),
ad_id varchar(100),
attr0 varchar(250),
scp_id varchar(250),
attr1 datetime null default null,
attr2 datetime null default null,
attr3 datetime null default null,
attr4 datetime null default null,
sent_date date null default null,
channel varchar(100)
)ENGINE=InnoDB;
CREATE INDEX idx_test_cid_sd ON test (client_id,sent_date);
CREATE INDEX idx_test_uid ON test (user_id);
CREATE INDEX idx_test_aid ON test (ad_id);
下面是我的查询 运行:
select
count(distinct user_id) as users
count(distinct ad_id) as ads
, count(attr1) as attr1
, count(attr2) as attr2
, count(attr3) as attr3
, count(attr4) as attr4
from test
where client_id = 'abcxyz'
and sent_date >= '2017-01-01' and sent_date < '2017-02-01';
问题:上面的查询花费了很多时间超过 1 小时才得到 return 结果。当我看到解释计划时,它正在使用索引并仅扫描 800 万条记录,但奇怪的问题是 return 结果花费了 1 个多小时。
谁能告诉我这里出了什么问题或对优化部分有什么建议吗?
您可以添加一个 covering index,仅包含 where
子句的列,但也包含结果的选定列。通过这种方式,查询可以从索引中读取整个结果,而不必读取一行。 where
子句中使用的列需要保留为索引的第一列,以便此索引可用于 where
限制。
CREATE INDEX idx_test_cid_sd_cover_all ON test
(client_id, sent_date, user_id, ad_id, attr1, attr2, attr3, attr4);
但是这个索引将比您现有的索引更大,因为几乎所有 table 的数据都将作为副本存在于索引中。
缩小 table 以减少对 I/O 的需求。这包括规范化(在可行的情况下)。为各种 ID 使用合理大小的 AUTO_INCREMENT
,而不是 VARCHAR
。如果您能解释这些 varchars,我可以评估这是否实用以及您可能获得多少好处。
有一个PRIMARY KEY
。 InnoDB 不喜欢没有。 (这对特定问题没有帮助。如果列的某些组合是 UNIQUE
,则将其设为 PK。如果不是,请使用 id INT UNSIGNED AUTO_INCREMENT
;它不会 运行 out ids 直到40亿后
更改 PRIMARY KEY
以使查询 运行 更快。 (虽然可能不比 Simulant 的 "covering" 索引快。)但它不会那么笨重:
假设你加上id .. AUTO_INCREMENT
,那么:
PRIMARY KEY(client_id, sent_date, id),
INDEX(id)
数据有多大(GB)?指数?您 可能 正处于 "too big to cache" 的风口浪尖,支付更多 RAM 可能会有所帮助。
- 摘要 table 适合
COUNT
,但不适合 COUNT(DISTINCT ...)
。也就是说,计数可以在几秒钟内完成。对于 Uniques,请参阅 my blog。 las,它相当粗略;请求帮忙。它提供与 COUNT
一样高效的汇总 COUNT(DISTINCT...)
,但有 1-2% 的误差。
摘要要点 table:PRIMARY KEY(client_id, day)
包含每天计数的列。然后获取一个月的值是 SUMming
31 天的计数。非常快。更多关于 Summary Tables.
下面是我创建的 table 结构和索引。 table 有 160 多万行。
create table test
(
client_id varchar(100),
user_id varchar(100),
ad_id varchar(100),
attr0 varchar(250),
scp_id varchar(250),
attr1 datetime null default null,
attr2 datetime null default null,
attr3 datetime null default null,
attr4 datetime null default null,
sent_date date null default null,
channel varchar(100)
)ENGINE=InnoDB;
CREATE INDEX idx_test_cid_sd ON test (client_id,sent_date);
CREATE INDEX idx_test_uid ON test (user_id);
CREATE INDEX idx_test_aid ON test (ad_id);
下面是我的查询 运行:
select
count(distinct user_id) as users
count(distinct ad_id) as ads
, count(attr1) as attr1
, count(attr2) as attr2
, count(attr3) as attr3
, count(attr4) as attr4
from test
where client_id = 'abcxyz'
and sent_date >= '2017-01-01' and sent_date < '2017-02-01';
问题:上面的查询花费了很多时间超过 1 小时才得到 return 结果。当我看到解释计划时,它正在使用索引并仅扫描 800 万条记录,但奇怪的问题是 return 结果花费了 1 个多小时。
谁能告诉我这里出了什么问题或对优化部分有什么建议吗?
您可以添加一个 covering index,仅包含 where
子句的列,但也包含结果的选定列。通过这种方式,查询可以从索引中读取整个结果,而不必读取一行。 where
子句中使用的列需要保留为索引的第一列,以便此索引可用于 where
限制。
CREATE INDEX idx_test_cid_sd_cover_all ON test
(client_id, sent_date, user_id, ad_id, attr1, attr2, attr3, attr4);
但是这个索引将比您现有的索引更大,因为几乎所有 table 的数据都将作为副本存在于索引中。
缩小 table 以减少对 I/O 的需求。这包括规范化(在可行的情况下)。为各种 ID 使用合理大小的
AUTO_INCREMENT
,而不是VARCHAR
。如果您能解释这些 varchars,我可以评估这是否实用以及您可能获得多少好处。有一个
PRIMARY KEY
。 InnoDB 不喜欢没有。 (这对特定问题没有帮助。如果列的某些组合是UNIQUE
,则将其设为 PK。如果不是,请使用id INT UNSIGNED AUTO_INCREMENT
;它不会 运行 out ids 直到40亿后更改
PRIMARY KEY
以使查询 运行 更快。 (虽然可能不比 Simulant 的 "covering" 索引快。)但它不会那么笨重:
假设你加上id .. AUTO_INCREMENT
,那么:
PRIMARY KEY(client_id, sent_date, id),
INDEX(id)
数据有多大(GB)?指数?您 可能 正处于 "too big to cache" 的风口浪尖,支付更多 RAM 可能会有所帮助。
- 摘要 table 适合
COUNT
,但不适合COUNT(DISTINCT ...)
。也就是说,计数可以在几秒钟内完成。对于 Uniques,请参阅 my blog。 las,它相当粗略;请求帮忙。它提供与COUNT
一样高效的汇总COUNT(DISTINCT...)
,但有 1-2% 的误差。
摘要要点 table:PRIMARY KEY(client_id, day)
包含每天计数的列。然后获取一个月的值是 SUMming
31 天的计数。非常快。更多关于 Summary Tables.