加快MySQL中的行计数

Kev*_*sen 41 mysql indexing optimization count

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

(身份,职称,身份)

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

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

SELECT status, COUNT(*) FROM books GROUP BY status
Run Code Online (Sandbox Code Playgroud)

或者专门找到有多少本书:

SELECT COUNT(*) FROM books WHERE status = "AVAILABLE"
Run Code Online (Sandbox Code Playgroud)

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

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

UPDATE

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

MyISAM无疑要快一点,但我很想知道是否有办法让等效查询运行更快(例如10-50毫秒 - 足够快,可以在低流量站点的每个网页请求上调用)没有缓存和触发器的精神开销.听起来答案是"没有办法快速运行直接查询"这是我的预期 - 我只是想确保我没有错过一个简单的替代方案.

Jos*_*vis 38

所以问题是

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

好吧,不是真的.使用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
//
Run Code Online (Sandbox Code Playgroud)


Sam*_*ron 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

这就是计算每个人可以看到的记录数不是那么直截了当的原因.

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


Alt*_*ife 8

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

InnoDB不保留表中的内部行数.(实际上,由于多版本化,这会有点复杂.)要处理SELECT COUNT(*)FROM t语句,InnoDB必须扫描表的索引,如果索引不完全在缓冲区中,这需要一些时间池.

建议的解决方案是:

要快速计数,您必须使用自己创建的计数器表,并让应用程序根据插入和删除更新它.如果近似行计数足够,也可以使用SHOW TABLE STATUS.

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

编写自己的解决方法.

  • 您引用的段落不适用于手头的案例.MyISAM仅在没有WHERE子句的情况下优化COUNT(*),这不是这里的情况. (6认同)

peu*_*feu 5

count(*)、count(status) 或 count(1) 之间没有显着差异

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

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

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

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

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

通常,对于高于“几个”的值,NO-ONE 对精确的实时计数感兴趣。无论如何,它是一个红鲱鱼,因为当您阅读它时,该值很可能已经改变了。