优化计数查询或重构设计?
Optimize a count query or restructure the design?
我正在尝试按范围日期(from/to 日期)为产品(大约 30k 产品将重复记录在数据库中)viewed/clicked 由用户做报告系统。
每次用户点击产品时,我都会在数据库的单行中记录 product_id 和日期。当我必须 select 并显示报告时,我的问题来了,因为 table 在 2 个月内迅速增长到 400 万,我必须保留长达 6 个月的记录。
我的问题是有没有更好的方法来优化查询或我记录它们的方式?
DB Table
CREATE TABLE `product_view` (
`id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`date_create` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `product_view`
ADD PRIMARY KEY (`id`),
ADD KEY `product_id` (`product_id`) USING BTREE;
我的 select 没有范围日期的查询需要大约 50 秒才能提取结果
SELECT SQL_NO_CACHE pd.name, pv.product_id, p.model, COUNT(pv.id) as total
FROM product_view pv
LEFT JOIN product p ON p.product_id = pv.product_id
LEFT JOIN product_description pd ON pd.product_id = pv.product_id
WHERE pv.product_id > 0
GROUP BY pv.product_id
ORDER BY total
DESC LIMIT 0,20
查询示例
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE pv range product_id product_id 4 NULL 1647717 Using where; Using index; Using temporary; Using filesort
1 SIMPLE p eq_ref PRIMARY PRIMARY 4 test.pv.product_id 1
1 SIMPLE pd ref PRIMARY,product_id PRIMARY 4 test.pv.product_id 1
日期范围查询
SELECT SQL_NO_CACHE pd.name, pv.product_id, p.model, COUNT(pv.id) as total
FROM product_view pv
LEFT JOIN product p ON p.product_id = pv.product_id
LEFT JOIN product_description pd ON pd.product_id = pv.product_id
WHERE pv.product_id > 0
AND DATE(pv.date_create) >= '2021-07-25'
AND DATE(pv.date_create) <= '2022-03-10'
GROUP BY pv.product_id
ORDER BY total DESC LIMIT 0,20
尝试将查询重写为相关查询:
select p.product_id, p.model, pd.name, (
select count(*)
from product_view as pv
where pv.product_id = p.product_id
and pv.date_create >= '2021-07-25'
and pv.date_create < '2022-03-10' + interval 1 day
) as total
from product as p
left join product_description as pd on p.product_id = pd.product_id
where exists (
select 1
from product_view as pv
where pv.product_id = p.product_id
and pv.date_create >= '2021-07-25'
and pv.date_create < '2022-03-10' + interval 1 day
-- this is a far more optimized version for dates used in your op
)
order by total desc
limit 0, 20
这不涉及分组,因此应该比您的原始查询更快。如果不需要日期过滤器,则从计数 sub-query.
中删除 where exists
部分和 and pv.date_create ...
其次,我在解释中没有看到任何有用的索引。您应该尝试以下索引:
create index ix1 on product_view (product_id, date_create)
-- should be (i) good for joining (ii) "covers" the date column
CREATE TABLE `product_view` (
-- toss, as useless: `id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`date_create` datetime NOT NULL,
PRIMARY KEY(product_id, date_create)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
然后PARTITION BY RANGE(TO_DAYS(date_create))
使删除更有效率。我建议每周进行大约 30 个分区。参见 Partition。
COUNT(pv.id)
-- 通常的模式就是 COUNT(*)
。使用 id,它会检查该 id 是否为 NOT NULL
,这是不必要的。
pv.product_id > 0
-- ids <=0 有什么特别之处吗?
让我们重新安排查询以“开始”计数:
SELECT pd.name, pv.product_id, p.model, s.total
FROM ( SELECT pv.product_id, COUNT(*) AS total
FROM product_view AS pv
WHERE pv.date_create >= '2021-07-25'
) AS s
JOIN product AS p ON p.product_id = pv.product_id
ORDER BY total DESC
LIMIT 0, 20
注:
- 摆脱
DATE()
,它不是“可搜索的”并且阻止使用任何索引。
- 如果你想计数昨天,然后添加
AND pv.date_create < CURDATE()
LEFT
表示可能缺少 'right' 行;我怀疑情况并非如此。
- 我摆脱了
pd
因为它不被使用(如果不被使用它会花费很多)。
SQL_NO_CACHE
将在 8.0 中消失;您现在也可以关闭查询缓存。
- 如果日期范围回溯到大部分数据,将扫描整个 table,因此我在这方面对性能帮助不大。所以...
- 建立并维护摘要 Table with (dy, product_id, subtotal);然后对其进行查询。参见 Summary Tables
我正在尝试按范围日期(from/to 日期)为产品(大约 30k 产品将重复记录在数据库中)viewed/clicked 由用户做报告系统。
每次用户点击产品时,我都会在数据库的单行中记录 product_id 和日期。当我必须 select 并显示报告时,我的问题来了,因为 table 在 2 个月内迅速增长到 400 万,我必须保留长达 6 个月的记录。
我的问题是有没有更好的方法来优化查询或我记录它们的方式?
DB Table
CREATE TABLE `product_view` (
`id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`date_create` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `product_view`
ADD PRIMARY KEY (`id`),
ADD KEY `product_id` (`product_id`) USING BTREE;
我的 select 没有范围日期的查询需要大约 50 秒才能提取结果
SELECT SQL_NO_CACHE pd.name, pv.product_id, p.model, COUNT(pv.id) as total
FROM product_view pv
LEFT JOIN product p ON p.product_id = pv.product_id
LEFT JOIN product_description pd ON pd.product_id = pv.product_id
WHERE pv.product_id > 0
GROUP BY pv.product_id
ORDER BY total
DESC LIMIT 0,20
查询示例
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE pv range product_id product_id 4 NULL 1647717 Using where; Using index; Using temporary; Using filesort
1 SIMPLE p eq_ref PRIMARY PRIMARY 4 test.pv.product_id 1
1 SIMPLE pd ref PRIMARY,product_id PRIMARY 4 test.pv.product_id 1
日期范围查询
SELECT SQL_NO_CACHE pd.name, pv.product_id, p.model, COUNT(pv.id) as total
FROM product_view pv
LEFT JOIN product p ON p.product_id = pv.product_id
LEFT JOIN product_description pd ON pd.product_id = pv.product_id
WHERE pv.product_id > 0
AND DATE(pv.date_create) >= '2021-07-25'
AND DATE(pv.date_create) <= '2022-03-10'
GROUP BY pv.product_id
ORDER BY total DESC LIMIT 0,20
尝试将查询重写为相关查询:
select p.product_id, p.model, pd.name, (
select count(*)
from product_view as pv
where pv.product_id = p.product_id
and pv.date_create >= '2021-07-25'
and pv.date_create < '2022-03-10' + interval 1 day
) as total
from product as p
left join product_description as pd on p.product_id = pd.product_id
where exists (
select 1
from product_view as pv
where pv.product_id = p.product_id
and pv.date_create >= '2021-07-25'
and pv.date_create < '2022-03-10' + interval 1 day
-- this is a far more optimized version for dates used in your op
)
order by total desc
limit 0, 20
这不涉及分组,因此应该比您的原始查询更快。如果不需要日期过滤器,则从计数 sub-query.
中删除where exists
部分和 and pv.date_create ...
其次,我在解释中没有看到任何有用的索引。您应该尝试以下索引:
create index ix1 on product_view (product_id, date_create)
-- should be (i) good for joining (ii) "covers" the date column
CREATE TABLE `product_view` (
-- toss, as useless: `id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`date_create` datetime NOT NULL,
PRIMARY KEY(product_id, date_create)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
然后PARTITION BY RANGE(TO_DAYS(date_create))
使删除更有效率。我建议每周进行大约 30 个分区。参见 Partition。
COUNT(pv.id)
-- 通常的模式就是 COUNT(*)
。使用 id,它会检查该 id 是否为 NOT NULL
,这是不必要的。
pv.product_id > 0
-- ids <=0 有什么特别之处吗?
让我们重新安排查询以“开始”计数:
SELECT pd.name, pv.product_id, p.model, s.total
FROM ( SELECT pv.product_id, COUNT(*) AS total
FROM product_view AS pv
WHERE pv.date_create >= '2021-07-25'
) AS s
JOIN product AS p ON p.product_id = pv.product_id
ORDER BY total DESC
LIMIT 0, 20
注:
- 摆脱
DATE()
,它不是“可搜索的”并且阻止使用任何索引。 - 如果你想计数昨天,然后添加
AND pv.date_create < CURDATE()
LEFT
表示可能缺少 'right' 行;我怀疑情况并非如此。- 我摆脱了
pd
因为它不被使用(如果不被使用它会花费很多)。 SQL_NO_CACHE
将在 8.0 中消失;您现在也可以关闭查询缓存。- 如果日期范围回溯到大部分数据,将扫描整个 table,因此我在这方面对性能帮助不大。所以...
- 建立并维护摘要 Table with (dy, product_id, subtotal);然后对其进行查询。参见 Summary Tables