加快 MySQL 中的行计数
Posted
技术标签:
【中文标题】加快 MySQL 中的行计数【英文标题】:Speeding up row counting in MySQL 【发布时间】:2010-11-22 21:19:50 【问题描述】:假设,出于说明目的,您正在使用一个简单的 mysql“books”表运行一个库,该表具有三列:
(id、标题、状态)
id 是主键 title 是书名 status 可以是描述图书当前状态的枚举(例如 AVAILABLE、CHECKEDOUT、PROCESSING、MISSING)报告每个州有多少书的简单查询是:
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 秒。在这两种情况下,count(*)、count(status) 或 count(1) 之间没有显着差异。
MyISAM 确实快了一点,但我很想知道是否有办法让等效查询运行得更快更多(例如 10-50 毫秒——快到可以被调用)低流量站点的每个网页请求),无需缓存和触发器的心理开销。听起来答案是“没有办法快速运行直接查询”,这正是我的预期——我只是想确保我没有错过一个简单的替代方案。
【问题讨论】:
使用:select count(indexed_column) from book 有区别吗? 你使用的是innodb还是myisam? @Boekwurm:它没有:)。 mysql 对查询进行了优化,因此 count(indexed_column)、count(*) 和 count(1) 以相同的效率返回。 【参考方案1】:这里的许多答案说索引无济于事,但在我的情况下它确实...
我的表使用了 MyISAM,并且只有大约 100k 行。查询:
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秒。
【讨论】:
您的第二个查询可能会命中查询缓存,无论索引如何,都会立即返回最后一个结果。 好点 - 我刚刚再次尝试查询(几天后,表的内容已被修改),计数耗时 0.02 秒。所以你对缓存的看法可能是对的,但索引似乎仍然有很大帮助。 在select后添加sql_no_cache以避免缓存 0.02s 可能意味着行数很少。COUNT(*)
,在没有缓存的情况下,所花费的时间与结果计数成正比。【参考方案2】:
count(*)、count(status) 或 count(1) 之间没有显着差异
count(column) 返回 column 不为 NULL 的行数。由于 1 不是 NULL,并且 status 也可能是 NOT NULL,因此数据库将优化测试并将它们全部转换为 count(*)。具有讽刺意味的是,这并不意味着“计算所有列都不为空的行”(或任何其他组合),它只是意味着“计算行”......
现在,回到你的问题,你不能吃蛋糕...
如果您希望“准确”计数始终可用,则必须通过触发器实时递增和递减,这会减慢写入速度
或者你可以使用count(*),但这会很慢
或者您可以接受粗略估计或过时值,并使用缓存或其他概率方法。
通常,对于高于“少数”的值,NO-ONE 对精确的实时计数感兴趣。无论如何,这是一条红鲱鱼,因为当你阅读它时,价值很可能已经改变了。
【讨论】:
【参考方案3】:所以问题是
有什么技术可以加快这类查询的速度吗?
嗯,不是真的。使用这些 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
//
【讨论】:
一个问题,视图而不是触发器怎么样?视图会比在原始表上运行查询更快吗? 不,在 MySQL 实现物化视图之前,它们的性能将与相应的 SELECT 语句大致相同。 但这难道不符合内部 SQL 逻辑吗? MySQL 是否保留行 ESTIMATE 因为不可能在没有重大性能问题的情况下保持行精确计数?例如,您在许多情况下都有单元格或行级锁定。这意味着您可以同时插入/删除两行,但如果您执行此操作则不能,因为所有内容都与单个数据相关联,一次只能编辑一个。 是否可以在我想使用 WHERE 子句获取计数的情况下使用它?比如只获取特定作者的列数?【参考方案4】:来自: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(*),这里不是这样。【参考方案5】:MyISAM 实际上使用 count(*) 非常快,缺点是 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
这就是计算有多少的原因 每个人都能看到的记录不是这样的 直截了当。
因此,最重要的是,如果您需要频繁且快速地获取此信息,您将需要以某种方式缓存计数,而不是直接访问表格。
【讨论】:
MyISAM 的COUNT
在没有WHERE
子句的情况下只有 很快。以上是关于加快 MySQL 中的行计数的主要内容,如果未能解决你的问题,请参考以下文章