为了便于说明,假设您使用简单的MySQL“books”表运行库,其中包含三列:
(id,title,status)
报告每个州有多少本书的简单查询是:
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毫秒 - 足够快以便被调用每个网页请求低流量网站)没有缓存和触发器的精神开销。听起来好像答案是“没有办法快速运行直接查询”这正是我所期望的 - 我只是想确保我没有错过一个简单的替代方案。
答案 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对精确的实时计数感兴趣。无论如何,这是一个红鲱鱼,因为当你阅读它时,价值很可能会发生变化。