优化查询
Optimize a query
我怎样才能使我的响应时间更快,大约平均响应时间为 0.2 秒(我的项目中有 8039 条记录table,我的跟踪中有 81 条记录table)
查询
SELECT a.name, b.cnt FROM `items` a LEFT JOIN
(SELECT guid, COUNT(*) cnt FROM tracking WHERE
date > UNIX_TIMESTAMP(NOW() - INTERVAL 1 day ) GROUP BY guid) b ON
a.`id` = b.guid WHERE a.`type` = 'streaming' AND a.`state` = 1
ORDER BY b.cnt DESC LIMIT 15 OFFSET 75
跟踪table结构
CREATE TABLE `tracking` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`guid` int(11) DEFAULT NULL,
`ip` int(11) NOT NULL,
`date` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `i1` (`ip`,`guid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4303 DEFAULT CHARSET=latin1;
项目table结构
CREATE TABLE `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`guid` int(11) DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`embed` varchar(255) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`description` text,
`tags` varchar(255) DEFAULT NULL,
`date` int(11) DEFAULT NULL,
`vote_val_total` float DEFAULT '0',
`vote_total` float(11,0) DEFAULT '0',
`rate` float DEFAULT '0',
`icon` text CHARACTER SET ascii,
`state` int(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9258 DEFAULT CHARSET=latin1;
你写的查询没有多大意义。它会在您的两个 table 中生成所有可能的行组合,然后将它们分组。
你可能想要这个:
SELECT a.*, b.cnt
FROM `items` a
LEFT JOIN (
SELECT guid, COUNT(*) cnt
FROM tracking
WHERE `date` > UNIX_TIMESTAMP(NOW() - INTERVAL 1 day)
GROUP BY guid
) b ON a.guid = b.guid
ORDER BY b.cnt DESC
本次查询的high-volume数据来自比较大的trackingtable。因此,您应该使用列 (date, guid)
为其添加复合索引。这将允许您查询 random-access date
的索引,然后扫描它以获得 guid
值。
ALTER TABLE tracking ADD INDEX guid_summary (`date`, guid);
我想您会看到不错的性能提升。
专业提示: 不要使用 SELECT *
。取而代之的是,给出你想要在结果集中出现的列的列表。例如,
SELECT a.guid, a.name, a.description, b.cnt
为什么这很重要?
首先,它使您的软件更有弹性,可以防止将来有人向您的 table 添加列。
其次,它告诉 MySQL 服务器只发送您想要的信息。这可以显着提高性能,尤其是当您的 table 变大时。
由于 tracking
的行数明显少于 items
,我将提出以下建议。
SELECT i.name, c.cnt
FROM
(
SELECT guid, COUNT(*) cnt
FROM tracking
WHERE date > UNIX_TIMESTAMP(NOW() - INTERVAL 1 day )
GROUP BY guid
) AS c
JOIN items AS i ON i.id = c.guid
WHERE i.type = 'streaming'
AND i.state = 1;
ORDER BY c.cnt DESC
LIMIT 15 OFFSET 75
它将无法显示任何 cnt
为 0 的项目。(您的版本显示计数为 NULL
的项目。)
需要的复合索引:
items: The PRIMARY KEY(id) is sufficient.
tracking: INDEX(date, guid) -- "covering"
其他问题:
- 如果
ip
是 IP-address,则需要 INT UNSIGNED
。但这仅涵盖 IPv4,不涵盖 IPv6。
- 好像
date
不仅仅是一个"date",而是一个日期+时间。请重命名它以避免混淆。
float(11,0)
-- 不要对整数使用 FLOAT
。不要在 FLOAT
或 DOUBLE
上使用 (m,n)
。 INT UNSIGNED
在这里更有意义。
OFFSET
在性能方面很顽皮——它必须扫描跳过的记录。但是,在您的查询中,无法避免收集 all 可能的行,对它们进行排序,超过 75 行,最后只提供 15 行。 (而且,不超过 81,就不会是完整的 15。)
您使用的是什么版本? LEFT JOIN ( SELECT ... )
的优化有重要的变化。请为讨论中的每个查询提供 EXPLAIN SELECT
。
我怎样才能使我的响应时间更快,大约平均响应时间为 0.2 秒(我的项目中有 8039 条记录table,我的跟踪中有 81 条记录table)
查询
SELECT a.name, b.cnt FROM `items` a LEFT JOIN
(SELECT guid, COUNT(*) cnt FROM tracking WHERE
date > UNIX_TIMESTAMP(NOW() - INTERVAL 1 day ) GROUP BY guid) b ON
a.`id` = b.guid WHERE a.`type` = 'streaming' AND a.`state` = 1
ORDER BY b.cnt DESC LIMIT 15 OFFSET 75
跟踪table结构
CREATE TABLE `tracking` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`guid` int(11) DEFAULT NULL,
`ip` int(11) NOT NULL,
`date` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `i1` (`ip`,`guid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4303 DEFAULT CHARSET=latin1;
项目table结构
CREATE TABLE `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`guid` int(11) DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`embed` varchar(255) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`description` text,
`tags` varchar(255) DEFAULT NULL,
`date` int(11) DEFAULT NULL,
`vote_val_total` float DEFAULT '0',
`vote_total` float(11,0) DEFAULT '0',
`rate` float DEFAULT '0',
`icon` text CHARACTER SET ascii,
`state` int(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9258 DEFAULT CHARSET=latin1;
你写的查询没有多大意义。它会在您的两个 table 中生成所有可能的行组合,然后将它们分组。
你可能想要这个:
SELECT a.*, b.cnt
FROM `items` a
LEFT JOIN (
SELECT guid, COUNT(*) cnt
FROM tracking
WHERE `date` > UNIX_TIMESTAMP(NOW() - INTERVAL 1 day)
GROUP BY guid
) b ON a.guid = b.guid
ORDER BY b.cnt DESC
本次查询的high-volume数据来自比较大的trackingtable。因此,您应该使用列 (date, guid)
为其添加复合索引。这将允许您查询 random-access date
的索引,然后扫描它以获得 guid
值。
ALTER TABLE tracking ADD INDEX guid_summary (`date`, guid);
我想您会看到不错的性能提升。
专业提示: 不要使用 SELECT *
。取而代之的是,给出你想要在结果集中出现的列的列表。例如,
SELECT a.guid, a.name, a.description, b.cnt
为什么这很重要?
首先,它使您的软件更有弹性,可以防止将来有人向您的 table 添加列。
其次,它告诉 MySQL 服务器只发送您想要的信息。这可以显着提高性能,尤其是当您的 table 变大时。
由于 tracking
的行数明显少于 items
,我将提出以下建议。
SELECT i.name, c.cnt
FROM
(
SELECT guid, COUNT(*) cnt
FROM tracking
WHERE date > UNIX_TIMESTAMP(NOW() - INTERVAL 1 day )
GROUP BY guid
) AS c
JOIN items AS i ON i.id = c.guid
WHERE i.type = 'streaming'
AND i.state = 1;
ORDER BY c.cnt DESC
LIMIT 15 OFFSET 75
它将无法显示任何 cnt
为 0 的项目。(您的版本显示计数为 NULL
的项目。)
需要的复合索引:
items: The PRIMARY KEY(id) is sufficient.
tracking: INDEX(date, guid) -- "covering"
其他问题:
- 如果
ip
是 IP-address,则需要INT UNSIGNED
。但这仅涵盖 IPv4,不涵盖 IPv6。 - 好像
date
不仅仅是一个"date",而是一个日期+时间。请重命名它以避免混淆。 float(11,0)
-- 不要对整数使用FLOAT
。不要在FLOAT
或DOUBLE
上使用(m,n)
。INT UNSIGNED
在这里更有意义。
OFFSET
在性能方面很顽皮——它必须扫描跳过的记录。但是,在您的查询中,无法避免收集 all 可能的行,对它们进行排序,超过 75 行,最后只提供 15 行。 (而且,不超过 81,就不会是完整的 15。)
您使用的是什么版本? LEFT JOIN ( SELECT ... )
的优化有重要的变化。请为讨论中的每个查询提供 EXPLAIN SELECT
。