加快MySQL中的行计数

时间:2009-08-26 06:08:23

标签: mysql optimization indexing count

为了便于说明,假设您使用简单的MySQL“books”表运行库,其中包含三列:

(id,title,status)

  • id 是主键
  • 标题是该书的标题
  • 状态可以是描述图书当前状态的枚举(例如,可用,检查,处理,丢失)

报告每个州有多少本书的简单查询是:

SELECT status, COUNT(*) FROM books GROUP BY status

或专门查找有多少本书:

SELECT COUNT(*) FROM books WHERE status = "AVAILABLE"

但是,一旦表增长到数百万行,这些查询需要几秒钟才能完成。在“状态”列中添加索引似乎不会对我的体验产生影响。

除了定期缓存结果或在每次书籍更改状态(通过触发器或其他机制)时在单独的表中显式更新摘要信息,是否有任何加速这些类型查询的技术?似乎COUNT查询最终查看每一行,并且(不知道更多细节)我有点惊讶,这些信息无法以某种方式从索引中确定。

更新

使用包含200万行的样本表(带有索引“状态”列),我对GROUP BY查询进行了基准测试。使用InnoDB存储引擎,查询在我的机器上需要3.0 - 3.2秒。使用MyISAM,查询需要0.9 - 1.1秒。在任何一种情况下,计数(*),计数(状态)或计数(1)之间没有显着差异。

MyISAM肯定要快一点,但我很想知道是否有办法让等效查询更快地运行(例如10-50毫秒 - 足够快以便被调用每个网页请求低流量网站)没有缓存和触发器的精神开销。听起来好像答案是“没有办法快速运行直接查询”这正是我所期望的 - 我只是想确保我没有错过一个简单的替代方案。

5 个答案:

答案 0 :(得分:37)

所以问题是

  

有加速这类查询的技术吗?

嗯,不是真的。使用SELECT COUNT(*)查询时,基于列的存储引擎可能会更快,但对于几乎任何其他查询而言,它的性能都会降低。

最好的办法是通过触发器维护汇总表。它没有太多开销,无论桌子有多大,SELECT部分​​都会瞬间完成。这是一些样板代码:

DELIMITER //

CREATE TRIGGER ai_books AFTER INSERT ON books
FOR EACH ROW UPDATE books_cnt SET total = total + 1 WHERE status = NEW.status
//
CREATE TRIGGER ad_books AFTER DELETE ON books
FOR EACH ROW UPDATE books_cnt SET total = total - 1 WHERE status = OLD.status;
//
CREATE TRIGGER au_books AFTER UPDATE ON books
FOR EACH ROW
BEGIN
    IF (OLD.status <> NEW.status)
    THEN
        UPDATE books_cnt SET total = total + IF(status = NEW.status, 1, -1) WHERE status IN (OLD.status, NEW.status);
    END IF;
END
//

答案 1 :(得分:9)

MyISAM实际上非常快,计数(*)的缺点是MyISAM存储不是那么可靠,最好避免数据完整性至关重要。

InnoDB执行count(*)类型查询的速度非常慢,因为它设计为允许同一数据的多个并发视图。所以在任何时候,它都不足以进入指数来获得计数。

来自:http://www.mail-archive.com/mysql@lists.mysql.com/msg120320.html

  

数据库以1000条记录开头   在它我开始一个交易你开始   一笔交易我删除了50条记录   添加50条记录我做COUNT()并查看   950条记录。你做一个COUNT()并看到   1050条记​​录。我提交了我的交易    - 除了你,数据库现在有950条记录。你提交你的   事务 - 数据库有1000   记录了。

     

InnoDB如何跟上哪些记录   是“可见的”或“可修改的”   尊重任何交易是通过   行级锁定,事务   隔离级别,和   多版本。   http://dev.mysql.com/doc/refman/4.1/en/innodb-transaction-model.html   http://dev.mysql.com/doc/refman/4.1/en/innodb-multi-versioning.html

     

这就是计算数量的原因   每个人都能看到的记录并非如此   直线前进。

所以,如果您需要经常快速地获取此信息,那么您需要以某种方式查看缓存计数,而不是去桌面。

答案 2 :(得分:8)

来自:http://dev.mysql.com/doc/refman/5.0/en/innodb-restrictions.html

  

InnoDB没有保留内部计数   表中的行数。 (在实践中,这个   因为有些复杂   多版本。)处理SELECT   COUNT(*)FROM t语句,InnoDB必须   扫描表的索引,其中   如果索引不是,则需要一些时间   完全在缓冲池中。

建议的解决方案是:

  

要获得快速计数,您必须使用   你自己创造的柜台表   让您的应用程序更新它   根据插入和删除   它确实。 SHOW TABLE STATUS也可以   如果是近似行数则使用   足够的。

简而言之:对于包含大量行的表,count(*)(在innoDB上)将花费很长时间。这是设计上的,无法帮助。

编写自己的解决方法。

答案 3 :(得分:3)

这里的许多答案都说索引不会有帮助,但在我的情况下它确实...

我的桌子使用了MyISAM,只有大约10万行。查询:

select count(*) from mytable where foreign_key_id=n

需要7-8秒才能完成。

我在foreign_key_id上添加了一个索引:

create index myindex on mytable (foreign_key_id) using btree;

创建索引后,上面的select语句报告执行时间为0.00秒。

答案 4 :(得分:2)

  

计数(*),计数(状态)或计数(1)

之间没有显着差异

count(列)返回列为NOT NULL的行数。由于1是非NULL,并且状态也可能是NOT NULL,因此数据库将优化测试并将它们全部转换为count(*)。具有讽刺意味的是,这并不意味着“计算所有列都不为空的行”(或任何其他组合),它只是意味着“计数行”......

现在,回到你的问题,你不能吃蛋糕并吃掉它......

  • 如果您希望“精确”计数始终可用,那么您必须通过触发器实时递增和递减,这会降低您的写入速度

  • 或者您可以使用count(*),但这会很慢

  • 或者您可以接受粗略估计或过时的价值,并使用缓存或其他概率方法。

通常,在大约“几”的值上,NO-ONE对精确的实时计数感兴趣。无论如何,这是一个红鲱鱼,因为当你阅读它时,价值很可能会发生变化。