即使使用where子句,“SELECT COUNT(*)”也很慢
我想弄清楚如何在MySQL中优化非常慢的查询(我没有devise这个):
SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391'; +----------+ | COUNT(*) | +----------+ | 3224022 | +----------+ 1 row in set (1 min 0.16 sec)
比较完整的计数:
select count(*) from change_event; +----------+ | count(*) | +----------+ | 6069102 | +----------+ 1 row in set (4.21 sec)
解释说明在这里没有帮助:
explain SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: me type: range possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: NULL rows: 4120213 Extra: Using where; Using index 1 row in set (0.00 sec)
好吧,它仍然认为它需要大约400万个条目来计算,但是我可以更快地计算一个文件中的行数! 我不明白为什么MySQL这么长时间。
这是表格定义:
CREATE TABLE `change_event` ( `change_event_id` bigint(20) NOT NULL default '0', `timestamp` datetime NOT NULL, `change_type` enum('create','update','delete','noop') default NULL, `changed_object_type` enum('Brand','Broadcast','Episode','OnDemand') NOT NULL, `changed_object_id` varchar(255) default NULL, `changed_object_modified` datetime NOT NULL default '1000-01-01 00:00:00', `modified` datetime NOT NULL default '1000-01-01 00:00:00', `created` datetime NOT NULL default '1000-01-01 00:00:00', `pid` char(15) default NULL, `episode_pid` char(15) default NULL, `import_id` int(11) NOT NULL, `status` enum('success','failure') NOT NULL, `xml_diff` text, `node_digest` char(32) default NULL, PRIMARY KEY (`change_event_id`), KEY `idx_change_events_changed_object_id` (`changed_object_id`), KEY `idx_change_events_episode_pid` (`episode_pid`), KEY `fk_import_id` (`import_id`), KEY `idx_change_event_timestamp_ce_id` (`timestamp`,`change_event_id`), KEY `idx_change_event_status` (`status`), CONSTRAINT `fk_change_event_import` FOREIGN KEY (`import_id`) REFERENCES `import` (`import_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
版:
$ mysql --version mysql Ver 14.12 Distrib 5.0.37, for pc-solaris2.8 (i386) using readline 5.0
有什么明显的我失踪? (是的,我已经尝试过“SELECT COUNT(change_event_id)”,但没有性能差异)。
InnoDB使用集群主键,所以主键与数据页中的行一起存储,而不是单独的索引页。 为了进行范围扫描,您仍然需要扫描数据页面中所有可能的宽行; 注意这个表格包含一个TEXT列。
我会尝试两件事情:
- 运行
optimize table
。 这将确保数据页以物理顺序存储。 这可以想象加快集群主键上的范围扫描。 - 仅在change_event_id列上创build附加的非主索引。 这将在索引页面中存储该列的副本,其扫描速度要快得多。 创build后,检查解释计划,以确保它使用新的索引。
(如果从零增加,你也可能想要使change_event_id列的bigint 无符号 )
这里有一些我build议的事情:
-
将列从“bigint”更改为“int unsigned”。 你真的有希望在这张桌子上有超过42亿logging吗? 如果不是的话,那么你就是在浪费空间(和时间)这个超宽的领域。 MySQL索引在较小的数据types上效率更高。
-
运行“ OPTIMIZE TABLE ”命令,查看后面的查询是否更快。
-
您也可以考虑根据ID字段对表进行分区 ,尤其是如果较旧的logging(具有较低的ID值)随着时间的推移变得不那么重要。 分区表通常可以比一个巨大的未分区表更快地执行聚合查询。
编辑:
仔细看看这个表格,它看起来像一个日志风格的表格,在这个表格中插入行但是从不修改。
如果这是真的,那么您可能不需要InnoDB存储引擎提供的所有交易安全性,而且您可能能够切换到MyISAM ,这对于聚合查询来说效率更高。
我已经遇到了这样的IP地理位置数据库的行为。 通过一些logging,MySQL从基于范围的查询的索引中获得任何优势的能力显然消失了。 对于地理位置数据库,我们通过将数据分割成足够合理的块来处理它,以便使用索引。
检查一下你的索引是如何分散的。 在我们公司,我们每晚都有一个导入过程,这个过程会影响我们的索引,并且随着时间的推移,它会对数据访问速度产生深远的影响 例如,我们有一个SQL过程需要花费2个小时才能运行一天,将索引拆分3分钟。 我们使用SQL Server 2005来查找可以在MySQL上查看这个脚本。
更新:看看这个链接: http : //dev.mysql.com/doc/refman/5.0/en/innodb-file-defragmenting.html
在该表上运行“ analyze table_name
表名称” – 索引可能不再是最佳的。
你可以经常通过运行“ show index from table_name
”来告诉它。 如果基数值为NULL
则需要强制重新分析。
MySQL首先会说“使用where”,因为它确实需要从索引数据中读取所有logging/值来实际计数它们。 随着InnoDb它也试图“抓”4百万logging范围来计算它。
您可能需要尝试不同的事务隔离级别: http : //dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-uncommitted
看哪一个更好
使用MyISAM将会很快,但是密集的写入模式会导致locking问题。
我会创build一个“计数器”表,并添加“创build行”/“删除行”触发器到你正在计数的表。 触发器应该在每个插入/删除的“计数器”表上增加/减less计数值,所以你不需要在每次需要时计算它们。
您也可以通过caching计数器在应用程序端完成此操作,但这将涉及在每次插入/删除时清除“计数器caching”。
对于一些参考看看这个http://pure.rednoize.com/2007/04/03/mysql-performance-use-counter-tables/