相同 MySql 查询执行时间长但存档时间短 table 多了 600 万条记录
Same MySql Query Long execution time but short on archive table with 6million more records
我对这个问题有点困惑。
我有一个 gps 跟踪应用程序,它将 gps 点记录到 track_log table 中。
当我对 运行ning 日志 table 进行基本查询时,大约需要 50 秒才能完成:
SELECT * FROM track_log WHERE node_id = '26' ORDER BY time_stamp DESC LIMIT 1
当我 运行 在存档 table 上执行完全相同的查询时,我将大部分日志复制到其中以减少 运行ning table 的日志到大约 120 万条记录。
存档 table 有 750 万条记录。
在同一台服务器上对存档 table 运行s 进行 0.1 秒的完全相同的查询,即使它大六倍!
怎么回事?
Here's the full Create Table schema:
CREATE TABLE `track_log` (
`id_track_log` INT(11) NOT NULL AUTO_INCREMENT,
`node_id` INT(11) DEFAULT NULL,
`client_id` INT(11) DEFAULT NULL,
`time_stamp` DATETIME NOT NULL,
`latitude` DOUBLE DEFAULT NULL,
`longitude` DOUBLE DEFAULT NULL,
`altitude` DOUBLE DEFAULT NULL,
`direction` DOUBLE DEFAULT NULL,
`speed` DOUBLE DEFAULT NULL,
`event_code` INT(11) DEFAULT NULL,
`event_description` VARCHAR(255) DEFAULT NULL,
`street_address` VARCHAR(255) DEFAULT NULL,
`mileage` INT(11) DEFAULT NULL,
`run_time` INT(11) DEFAULT NULL,
`satellites` INT(11) DEFAULT NULL,
`gsm_signal_status` DOUBLE DEFAULT NULL,
`hor_pos_accuracy` double DEFAULT NULL,
`positioning_status` char(1) DEFAULT NULL,
`io_port_status` char(16) DEFAULT NULL,
`AD1` decimal(10,2) DEFAULT NULL,
`AD2` decimal(10,2) DEFAULT NULL,
`AD3` decimal(10,2) DEFAULT NULL,
`battery_voltage` decimal(10,2) DEFAULT NULL,
`ext_power_voltage` decimal(10,2) DEFAULT NULL,
`rfid` char(8) DEFAULT NULL,
`pic_name` varchar(255) DEFAULT NULL,
`temp_sensor_no` char(2) DEFAULT NULL,
PRIMARY KEY (`id_track_log`),
UNIQUE KEY `id_track_log_UNIQUE` (`id_track_log`),
KEY `client_id_fk_idx` (`client_id`),
KEY `track_log_node_id_fk_idx` (`node_id`),
KEY `track_log_event_code_fk_idx` (`event_code`),
KEY `track_log_time_stamp_index` (`time_stamp`),
CONSTRAINT `track_log_client_id` FOREIGN KEY (`client_id`) REFERENCES `clients` (`client_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `track_log_event_code_fk` FOREIGN KEY (`event_code`) REFERENCES `event_codes` (`event_code`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `track_log_node_id_fk` FOREIGN KEY (`node_id`) REFERENCES `nodes` (`id_nodes`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=8632967 DEFAULT CHARSET=utf8
TL;DR
- 确保在两个 table 中都定义了索引,对于此查询,
node_id
和 time_stamp
是好的索引。
- 对您的 table 进行碎片整理:https://dev.mysql.com/doc/refman/5.5/en/innodb-file-defragmenting.html(这可能会有所帮助,但不会产生太大影响)。
- 确保您的查询未被其他查询阻止。如果在
track_log
table 中连续插入数据,这些查询可能会阻止您的查询。您可以通过更改事务隔离级别来防止这种情况,请参阅 https://dev.mysql.com/doc/refman/5.5/en/set-transaction.html 了解更多信息。 注意:小心这个!
索引
我猜这与您在 table 上定义的索引有关。你能 post SHOW CREATE TABLES track_log
输出和你的档案 table 的输出吗?您正在执行的查询需要 node_id
和 time_stamp
上的索引以获得最佳性能。
碎片整理
除了您在 table 上定义的索引外,这可能与数据碎片有关。我假设您现在使用 InnoDB 作为 table 引擎。根据您的设置,数据库中的每个 table 都存储在一个单独的文件中,或者数据库中的每个 table 都存储在一个文件中(innodb_file_per_table
变量)。这些文件的大小永远不会缩小。如果您的 track_log
table 已增长到 870 万条记录,在磁盘上,它仍然占用 space 所有这 870 万条记录。
如果您已将记录从 track_log
table 移动到存档 table,数据可能仍位于 [=12] 的物理文件的开头和结尾=].如果 time_stamp
处未定义索引,则仍需要完整 table 扫描才能按时间戳排序。这意味着:从磁盘读取完整的文件。因为您删除的记录仍然在文件中占用 space,所以这可能会有所不同。
编辑:
事务
其他交易可能会阻止您的 SELECT
查询。 InnoDB 引擎可能会发生这种情况。如果您不断地向 track_log
table 中插入大量数据,这些查询可能会阻止您的查询。它将必须等到在此 table.
上没有其他事务正在执行
有办法解决这个问题,但你应该小心。您可以更改查询的事务隔离级别。通过将事务隔离级别设置为 READ UNCOMMITTED
您将能够读取数据,而其他插入是 运行。但它可能并不总是为您提供最新数据。如果你想牺牲这取决于你的情况。如果稍后要更改数据并更新数据,通常不想更改事务隔离级别。但是,例如,当显示不应始终准确和最新的统计信息时,这可能会真正加快您的查询速度。
当我需要显示定期更新的大型 table 的统计数据时,我有时会自己使用它。
让我们分析您的查询:
SELECT * FROM track_log WHERE node_id = '26' ORDER BY time_stamp DESC LIMIT 1
上述查询首先根据 time_stamp
对 table 中存在的所有数据进行排序,然后 returns 对顶行进行排序。
但是,当在 archived table
上执行此查询时,order by
子句可能会被忽略(基于压缩和系统设置),因此它 returns 它遇到的第一行table.
您可以通过将结果与实际的最新行进行比较来验证存档 table 的输出。
这几乎可以肯定是因为您的存档 table 的索引优于 track_log table。
为了有效地满足此查询,您需要一个复合索引 (node_id, time_stamp)
为什么这样做有效?因为 InnoDB 和 MyISAM 索引是所谓的 BTREE 索引,这意味着我们按顺序搜索它们的直觉会起作用。您的查询查找 node_id
的特定值,这意味着它可以有效地跳转到索引中的该值。查询然后调用与该 node_id
值相关的 time_stamp
的最高可能值。现在它在同一个索引中,并且以正确的顺序也可以快速访问它。因此,您需要的行可以随机访问,并且 MySQL 不必通过逐行扫描 table 来寻找它。扫描几乎肯定是您查询中花费时间的原因。
要记住三件事:
一:单列上的大量索引无法像精心选择的复合索引那样帮助查询。阅读此 http://use-the-index-luke.com/
二:SELECT *
通常对 table 有害,列数与您显示的列一样多。相反,您应该在 SELECT
查询中枚举您实际需要的列。这样 MySQL 就不必传输那么多数据了。
三:DOUBLE
数据类型对于商业级 GPS 数据来说太过分了。 FLOAT
足够精确。
我对这个问题有点困惑。 我有一个 gps 跟踪应用程序,它将 gps 点记录到 track_log table 中。 当我对 运行ning 日志 table 进行基本查询时,大约需要 50 秒才能完成:
SELECT * FROM track_log WHERE node_id = '26' ORDER BY time_stamp DESC LIMIT 1
当我 运行 在存档 table 上执行完全相同的查询时,我将大部分日志复制到其中以减少 运行ning table 的日志到大约 120 万条记录。 存档 table 有 750 万条记录。
在同一台服务器上对存档 table 运行s 进行 0.1 秒的完全相同的查询,即使它大六倍! 怎么回事?
Here's the full Create Table schema:
CREATE TABLE `track_log` (
`id_track_log` INT(11) NOT NULL AUTO_INCREMENT,
`node_id` INT(11) DEFAULT NULL,
`client_id` INT(11) DEFAULT NULL,
`time_stamp` DATETIME NOT NULL,
`latitude` DOUBLE DEFAULT NULL,
`longitude` DOUBLE DEFAULT NULL,
`altitude` DOUBLE DEFAULT NULL,
`direction` DOUBLE DEFAULT NULL,
`speed` DOUBLE DEFAULT NULL,
`event_code` INT(11) DEFAULT NULL,
`event_description` VARCHAR(255) DEFAULT NULL,
`street_address` VARCHAR(255) DEFAULT NULL,
`mileage` INT(11) DEFAULT NULL,
`run_time` INT(11) DEFAULT NULL,
`satellites` INT(11) DEFAULT NULL,
`gsm_signal_status` DOUBLE DEFAULT NULL,
`hor_pos_accuracy` double DEFAULT NULL,
`positioning_status` char(1) DEFAULT NULL,
`io_port_status` char(16) DEFAULT NULL,
`AD1` decimal(10,2) DEFAULT NULL,
`AD2` decimal(10,2) DEFAULT NULL,
`AD3` decimal(10,2) DEFAULT NULL,
`battery_voltage` decimal(10,2) DEFAULT NULL,
`ext_power_voltage` decimal(10,2) DEFAULT NULL,
`rfid` char(8) DEFAULT NULL,
`pic_name` varchar(255) DEFAULT NULL,
`temp_sensor_no` char(2) DEFAULT NULL,
PRIMARY KEY (`id_track_log`),
UNIQUE KEY `id_track_log_UNIQUE` (`id_track_log`),
KEY `client_id_fk_idx` (`client_id`),
KEY `track_log_node_id_fk_idx` (`node_id`),
KEY `track_log_event_code_fk_idx` (`event_code`),
KEY `track_log_time_stamp_index` (`time_stamp`),
CONSTRAINT `track_log_client_id` FOREIGN KEY (`client_id`) REFERENCES `clients` (`client_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `track_log_event_code_fk` FOREIGN KEY (`event_code`) REFERENCES `event_codes` (`event_code`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `track_log_node_id_fk` FOREIGN KEY (`node_id`) REFERENCES `nodes` (`id_nodes`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=8632967 DEFAULT CHARSET=utf8
TL;DR
- 确保在两个 table 中都定义了索引,对于此查询,
node_id
和time_stamp
是好的索引。 - 对您的 table 进行碎片整理:https://dev.mysql.com/doc/refman/5.5/en/innodb-file-defragmenting.html(这可能会有所帮助,但不会产生太大影响)。
- 确保您的查询未被其他查询阻止。如果在
track_log
table 中连续插入数据,这些查询可能会阻止您的查询。您可以通过更改事务隔离级别来防止这种情况,请参阅 https://dev.mysql.com/doc/refman/5.5/en/set-transaction.html 了解更多信息。 注意:小心这个!
索引
我猜这与您在 table 上定义的索引有关。你能 post SHOW CREATE TABLES track_log
输出和你的档案 table 的输出吗?您正在执行的查询需要 node_id
和 time_stamp
上的索引以获得最佳性能。
碎片整理
除了您在 table 上定义的索引外,这可能与数据碎片有关。我假设您现在使用 InnoDB 作为 table 引擎。根据您的设置,数据库中的每个 table 都存储在一个单独的文件中,或者数据库中的每个 table 都存储在一个文件中(innodb_file_per_table
变量)。这些文件的大小永远不会缩小。如果您的 track_log
table 已增长到 870 万条记录,在磁盘上,它仍然占用 space 所有这 870 万条记录。
如果您已将记录从 track_log
table 移动到存档 table,数据可能仍位于 [=12] 的物理文件的开头和结尾=].如果 time_stamp
处未定义索引,则仍需要完整 table 扫描才能按时间戳排序。这意味着:从磁盘读取完整的文件。因为您删除的记录仍然在文件中占用 space,所以这可能会有所不同。
编辑:
事务
其他交易可能会阻止您的 SELECT
查询。 InnoDB 引擎可能会发生这种情况。如果您不断地向 track_log
table 中插入大量数据,这些查询可能会阻止您的查询。它将必须等到在此 table.
有办法解决这个问题,但你应该小心。您可以更改查询的事务隔离级别。通过将事务隔离级别设置为 READ UNCOMMITTED
您将能够读取数据,而其他插入是 运行。但它可能并不总是为您提供最新数据。如果你想牺牲这取决于你的情况。如果稍后要更改数据并更新数据,通常不想更改事务隔离级别。但是,例如,当显示不应始终准确和最新的统计信息时,这可能会真正加快您的查询速度。
当我需要显示定期更新的大型 table 的统计数据时,我有时会自己使用它。
让我们分析您的查询:
SELECT * FROM track_log WHERE node_id = '26' ORDER BY time_stamp DESC LIMIT 1
上述查询首先根据 time_stamp
对 table 中存在的所有数据进行排序,然后 returns 对顶行进行排序。
但是,当在 archived table
上执行此查询时,order by
子句可能会被忽略(基于压缩和系统设置),因此它 returns 它遇到的第一行table.
您可以通过将结果与实际的最新行进行比较来验证存档 table 的输出。
这几乎可以肯定是因为您的存档 table 的索引优于 track_log table。
为了有效地满足此查询,您需要一个复合索引 (node_id, time_stamp)
为什么这样做有效?因为 InnoDB 和 MyISAM 索引是所谓的 BTREE 索引,这意味着我们按顺序搜索它们的直觉会起作用。您的查询查找 node_id
的特定值,这意味着它可以有效地跳转到索引中的该值。查询然后调用与该 node_id
值相关的 time_stamp
的最高可能值。现在它在同一个索引中,并且以正确的顺序也可以快速访问它。因此,您需要的行可以随机访问,并且 MySQL 不必通过逐行扫描 table 来寻找它。扫描几乎肯定是您查询中花费时间的原因。
要记住三件事:
一:单列上的大量索引无法像精心选择的复合索引那样帮助查询。阅读此 http://use-the-index-luke.com/
二:SELECT *
通常对 table 有害,列数与您显示的列一样多。相反,您应该在 SELECT
查询中枚举您实际需要的列。这样 MySQL 就不必传输那么多数据了。
三:DOUBLE
数据类型对于商业级 GPS 数据来说太过分了。 FLOAT
足够精确。