MySQL 日历 table 慢的子查询计数
MySQL subquery count with calendar table slow
我在 MySQL (InnoDB) 中有销售额 table。它有 +- 100 万条记录。我想展示一些漂亮的图表。获取正确的数据不是问题。快速获取它是...
所以我喜欢计算 table A 组每天(稍后还有月、年)从 A 期到 Z 期的销售额。具体;在过去的 30 天里,我想知道我们每天在数据库中有多少销售记录。
所以 MySQL 必须 return 像这样:
我喜欢实现 MySQL return 数据如下:
date, count
2017-04-01, 2482
2017-04-02, 1934
2017-04-03, 2701
...
Sales的结构基本是这样的:
CREATE TABLE `sales` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `contacts_created_at_index` (`created_at`),
KEY `contacts_deleted_at_index` (`deleted_at`),
KEY `ind_created_at_deleted_at` (`created_at`,`deleted_at`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
有些天(数据点)可能没有任何结果,但我不喜欢数据中存在间隙。所以我也有一些 'calendar' table.
CREATE TABLE `time_dimension` (
`id` int(11) NOT NULL,
`db_date` date NOT NULL,
`year` int(11) NOT NULL,
`month` int(11) NOT NULL,
`day` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `td_ymd_idx` (`year`,`month`,`day`),
UNIQUE KEY `td_dbdate_idx` (`db_date`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
每天用计数获取 30 行(30 天)需要 30 秒...
这是我尝试的第一个查询:
SELECT
`db_date` AS `date`,
(SELECT
COUNT(1)
FROM
sales
WHERE
DATE(created_at) = db_date) AS count
FROM
`time_dimension`
WHERE
`db_date` >= '2017-04-11'
AND `db_date` <= '2017-04-25'
ORDER BY `db_date` ASC
但是就像我说的那样它真的很慢(11.9 秒)。我尝试了各种其他方法,但没有运气。例如:
SELECT time_dimension.db_date AS DATE,
COUNT(1) AS count
FROM sales RIGHT JOIN time_dimension ON (DATE(sales.created_at) =
time_dimension.db_date)
WHERE
(time_dimension.db_date BETWEEN '2017-03-11' AND '2017-04-11')
GROUP BY
DATE
仅查询 1 个数据点仅需 5.4 毫秒:
SELECT COUNT(1) FROM sales WHERE created_at BETWEEN '2017-04-11 00:00:00' AND '2017-04-25 23:59:59'
我还没有在我的本地机器上检查 innodb_buffer_poolsize。我也会检查一下。关于如何快速进行查询的任何想法?将来我什至需要 where 子句和连接来过滤销售记录集..
谢谢。
尼克
您可以尝试先统计销售数据,然后将统计结果与您的 日历 table.
SELECT time_dimension.db_date AS date,
by_date.sale_count
FROM time_dimension
LEFT JOIN (SELECT DATE(sales.created_at) sale_date,
COUNT(1) AS sale_count
FROM sales
WHERE created_at BETWEEN '2017-03-11 00:00:00' AND
'2017-04-11 23:59:59'
GROUP BY DATE(sales.created_at)) by_date
ON time_dimension.db_date = by_date.sale_date
WHERE time_dimension.db_date BETWEEN '2017-03-11' AND '2017-04-11'
您的查询中有问题的部分是数据类型转换 DATE(created_at)
,这有效地阻止了 Mysql 使用位于 created_at
的索引。
您的 1 datapoint
查询避免了这种情况,这就是它工作速度很快的原因。
要解决此问题,您应该检查 created_at
是否在特定日期的范围内,例如:
created_at BETWEEN db_date AND DATE_ADD(db_date,INTERVAL 1 DAY)
这样 Mysql 就可以根据需要使用索引(进行范围查找)。
WHERE DATE(created_at) = db_date)
-->
WHERE created_at >= db_date
AND created_at < db_date + INTERVAL 1 DAY
- 这避免了包括第二天的午夜(正如
BETWEEN
那样)
- 适用于所有口味:
DATE
、DATETIME
、DATETIME(6)
- 不将
created_at
隐藏在索引看不到的函数中。
对于time_dimension
,去掉PRIMARY KEY (id)
,把UNIQUE(db_date)
改成PK
进行这些更改后,您的原始子查询可能会与 LEFT JOIN ( SELECT ... )
竞争。 (这取决于 MySQL 的版本。)
我在 MySQL (InnoDB) 中有销售额 table。它有 +- 100 万条记录。我想展示一些漂亮的图表。获取正确的数据不是问题。快速获取它是...
所以我喜欢计算 table A 组每天(稍后还有月、年)从 A 期到 Z 期的销售额。具体;在过去的 30 天里,我想知道我们每天在数据库中有多少销售记录。
所以 MySQL 必须 return 像这样:
我喜欢实现 MySQL return 数据如下:
date, count
2017-04-01, 2482
2017-04-02, 1934
2017-04-03, 2701
...
Sales的结构基本是这样的:
CREATE TABLE `sales` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `contacts_created_at_index` (`created_at`),
KEY `contacts_deleted_at_index` (`deleted_at`),
KEY `ind_created_at_deleted_at` (`created_at`,`deleted_at`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
有些天(数据点)可能没有任何结果,但我不喜欢数据中存在间隙。所以我也有一些 'calendar' table.
CREATE TABLE `time_dimension` (
`id` int(11) NOT NULL,
`db_date` date NOT NULL,
`year` int(11) NOT NULL,
`month` int(11) NOT NULL,
`day` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `td_ymd_idx` (`year`,`month`,`day`),
UNIQUE KEY `td_dbdate_idx` (`db_date`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
每天用计数获取 30 行(30 天)需要 30 秒...
这是我尝试的第一个查询:
SELECT
`db_date` AS `date`,
(SELECT
COUNT(1)
FROM
sales
WHERE
DATE(created_at) = db_date) AS count
FROM
`time_dimension`
WHERE
`db_date` >= '2017-04-11'
AND `db_date` <= '2017-04-25'
ORDER BY `db_date` ASC
但是就像我说的那样它真的很慢(11.9 秒)。我尝试了各种其他方法,但没有运气。例如:
SELECT time_dimension.db_date AS DATE,
COUNT(1) AS count
FROM sales RIGHT JOIN time_dimension ON (DATE(sales.created_at) =
time_dimension.db_date)
WHERE
(time_dimension.db_date BETWEEN '2017-03-11' AND '2017-04-11')
GROUP BY
DATE
仅查询 1 个数据点仅需 5.4 毫秒:
SELECT COUNT(1) FROM sales WHERE created_at BETWEEN '2017-04-11 00:00:00' AND '2017-04-25 23:59:59'
我还没有在我的本地机器上检查 innodb_buffer_poolsize。我也会检查一下。关于如何快速进行查询的任何想法?将来我什至需要 where 子句和连接来过滤销售记录集..
谢谢。
尼克
您可以尝试先统计销售数据,然后将统计结果与您的 日历 table.
SELECT time_dimension.db_date AS date,
by_date.sale_count
FROM time_dimension
LEFT JOIN (SELECT DATE(sales.created_at) sale_date,
COUNT(1) AS sale_count
FROM sales
WHERE created_at BETWEEN '2017-03-11 00:00:00' AND
'2017-04-11 23:59:59'
GROUP BY DATE(sales.created_at)) by_date
ON time_dimension.db_date = by_date.sale_date
WHERE time_dimension.db_date BETWEEN '2017-03-11' AND '2017-04-11'
您的查询中有问题的部分是数据类型转换 DATE(created_at)
,这有效地阻止了 Mysql 使用位于 created_at
的索引。
您的 1 datapoint
查询避免了这种情况,这就是它工作速度很快的原因。
要解决此问题,您应该检查 created_at
是否在特定日期的范围内,例如:
created_at BETWEEN db_date AND DATE_ADD(db_date,INTERVAL 1 DAY)
这样 Mysql 就可以根据需要使用索引(进行范围查找)。
WHERE DATE(created_at) = db_date)
-->
WHERE created_at >= db_date
AND created_at < db_date + INTERVAL 1 DAY
- 这避免了包括第二天的午夜(正如
BETWEEN
那样) - 适用于所有口味:
DATE
、DATETIME
、DATETIME(6)
- 不将
created_at
隐藏在索引看不到的函数中。
对于time_dimension
,去掉PRIMARY KEY (id)
,把UNIQUE(db_date)
改成PK
进行这些更改后,您的原始子查询可能会与 LEFT JOIN ( SELECT ... )
竞争。 (这取决于 MySQL 的版本。)