【问题标题】:How to optimize COUNT(*) performance on InnoDB by using index如何使用索引优化 InnoDB 上的 COUNT(*) 性能
【发布时间】:2013-10-16 13:04:54
【问题描述】:

我有一个较大但很窄的 InnoDB 表,其中有大约 9m 条记录。在桌子上做count(*)count(id) 非常慢(6 秒以上):

DROP TABLE IF EXISTS `perf2`;

CREATE TABLE `perf2` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `channel_id` int(11) DEFAULT NULL,
  `timestamp` bigint(20) NOT NULL,
  `value` double NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ts_uniq` (`channel_id`,`timestamp`),
  KEY `IDX_CHANNEL_ID` (`channel_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

RESET QUERY CACHE;
SELECT COUNT(*) FROM perf2;

虽然该语句不经常运行,但最好对其进行优化。根据http://www.cloudspace.com/blog/2009/08/06/fast-mysql-innodb-count-really-fast/,这应该可以通过强制 InnoDB 使用索引来实现:

SELECT COUNT(id) FROM perf2 USE INDEX (PRIMARY);

解释计划似乎很好:

id  select_type table   type    possible_keys   key     key_len ref     rows    Extra
1   SIMPLE      perf2   index   NULL            PRIMARY 4       NULL    8906459 Using index

不幸的是,声明和以前一样慢。根据"SELECT COUNT(*)" is slow, even with where clause,我也尝试过优化表格但没有成功。

在 InnoDB 上优化 COUNT(*) 性能的方法是什么?

【问题讨论】:

  • 更改为 MyISAM 可以创造奇迹 - 在 PHPMyAdmin 中只需单击一次。)
  • @davidkonrad 执行外键和使用事务也需要花费。
  • @Jim,现在我明白了,我认为您的意思是事务或外键对于 MyISAM 是强制性的,我认为不是。被误解的“强制执行”
  • @davidkonrad:是的,但不是问的问题:/
  • channel_id 上的索引与以该列开头的索引是多余的;放弃前者。

标签: mysql innodb


【解决方案1】:

基于@Che 代码,您还可以在INSERTUPDATEperf2 上使用触发器,以便实时更新统计表中的值。

CREATE TABLE stats (
 `key`   varchar(50)  NOT NULL PRIMARY KEY,
 `value` varchar(100) NOT NULL
);

然后:

CREATE TRIGGER `count_up` AFTER INSERT   ON `perf2` FOR EACH ROW UPDATE `stats`
SET   `stats`.`value` = `stats`.`value` + 1 
WHERE `stats`.`key` = 'perf2_count';

CREATE TRIGGER `count_down` AFTER DELETE ON `perf2` FOR EACH ROW UPDATE `stats`
SET   `stats`.`value` = `stats`.`value` - 1 
WHERE `stats`.`key` = 'perf2_count';

所以perf2 表中的行数可以使用此查询实时读取:

SELECT `value` FROM `stats` WHERE `key` = 'perf2_count';

这将具有消除执行COUNT(*) 的性能问题的优势,并且只会在perf2 中的数据更改时执行。

【讨论】:

  • 这个答案非常有趣、高效且实时。如果您认为此答案令人困惑,那只是因为它是此页面中唯一以正确方式使用顶点的答案。
【解决方案2】:

从 MySQL 5.1.6 开始,您可以使用 Event Scheduler 并定期将计数插入到统计表中。

首先创建一个表来保存计数:

CREATE TABLE stats (
`key` varchar(50) NOT NULL PRIMARY KEY,
`value` varchar(100) NOT NULL);

然后创建一个事件来更新表格:

CREATE EVENT update_stats
ON SCHEDULE
  EVERY 5 MINUTE
DO
  INSERT INTO stats (`key`, `value`)
  VALUES ('data_count', (select count(id) from data))
  ON DUPLICATE KEY UPDATE value=VALUES(value);

它并不完美,但它提供了一个自包含的解决方案(没有 cronjob 或队列),可以轻松定制以根据计数所需的新鲜度运行。

【讨论】:

  • 不知道这些事件,谢谢。对于我运行数据库的小型 RasPi 来说,它仍然是一个杀手 :(
  • 这个很好用又好用
【解决方案3】:

目前我已经通过使用这个近似值解决了这个问题:

EXPLAIN SELECT COUNT(id) FROM data USE INDEX (PRIMARY)

如上图所示,使用 InnoDB 时,可以从解释计划的 rows 列中读取大致的行数。当使用 MyISAM 时,这将保持为 EMPTY,因为表引用正在被优化掉 - 所以如果空的回退到传统的 SELECT COUNT 代替。

【讨论】:

  • 请记住,这里的“近似值”远非准确,对于具有 1M 行的表,可能返回 100K 到 10M 之间的任何值。 SHOW TABLE STATUS 类似。真是不靠谱。不过,我认为 MySQL 5.7 中修复了很多性能问题。
  • 这个近似值非常不可靠,我想不出我会使用它的情况。对于大表,我宁愿定期执行完整的COUNT(*) 并使用该值直到下一次计数。
  • SHOW TABLE STATUS 中的Auto_increment 字段对于整个表的计数更加准确,速度也更快。
  • @SamDoidge 小心如果已经从表中删除了记录,那么 Auto_increment 将被高估。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-25
  • 1970-01-01
相关资源
最近更新 更多