如何使用索引优化InnoDB上的COUNT(*)性能

and*_*dig 26 mysql innodb

我有一个很大但很窄的InnoDB表,记录大约9米.在桌子上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;
Run Code Online (Sandbox Code Playgroud)

虽然声明不经常运行,但优化它会很好.根据http://www.cloudspace.com/blog/2009/08/06/fast-mysql-innodb-count-really-fast/,这可以通过强制InnoDB使用索引来实现:

SELECT COUNT(id) FROM perf2 USE INDEX (PRIMARY);
Run Code Online (Sandbox Code Playgroud)

解释计划似乎很好:

id  select_type table   type    possible_keys   key     key_len ref     rows    Extra
1   SIMPLE      perf2   index   NULL            PRIMARY 4       NULL    8906459 Using index
Run Code Online (Sandbox Code Playgroud)

不幸的是,声明和以前一样慢.根据"SELECT COUNT(*)"很慢,即使使用where子句,我也尝试优化表而没有成功.

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

and*_*dig 17

暂时我通过使用这个近似解决了这个问题:

EXPLAIN SELECT COUNT(id) FROM data USE INDEX (PRIMARY)
Run Code Online (Sandbox Code Playgroud)

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

  • 请记住,这里的"近似"远非准确,对于具有1M行的表,可能会返回100K到10M之间的任何值.`SHOW TABLE STATUS`是类似的.这真的不可靠.我认为很多性能问题都在MySQL 5.7中得到修复. (15认同)
  • 这种近似非常难以置信,我无法想到我会使用它的情况.对于一个大表,我宁愿定期执行完整的`COUNT(*)`并使用该值直到下一次计数. (5认同)
  • “SHOW TABLE STATUS”中的“Auto_increment”字段对于整个表的计数更加准确,速度也更快。 (2认同)

Che*_*Che 16

从MySQL 5.1.6开始,您可以使用事件调度程序并定期将计数插入统计表.

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

CREATE TABLE stats (
`key` varchar(50) NOT NULL PRIMARY KEY,
`value` varchar(100) NOT NULL);
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

它并不完美,但它提供了一个独立的解决方案(没有cronjob或队列),可以轻松定制,以便按照计数所需的新鲜度运行.


MQu*_*ion 14

基于@Che代码,您还可以在INSERT和UPDATE上使用触发器来执行perf2,以使stats表中的值保持最新.

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";
Run Code Online (Sandbox Code Playgroud)

这将具有消除执行计数(*)的性能问题的优点,并且仅在表perf2中的数据改变时执行