在MySQL中加快行数

假设,出于说明的目的,您正在运行一个使用简单的MySQL“books”表的三个列的库:

(ID,标题,状态)

  • id是主键
  • 标题是书的标题
  • 状态可能是描述本书当前状态的枚举(例如,AVAILABLE,CHECKEDOUT,PROCESSING,MISSING)

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

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

或者专门查找有多less本书可用:

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

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

除了定期caching结果,或者在每次书籍更改状态(通过触发器或其他机制)时,在一个单独的表格中显式更新汇总信息,有没有什么技术可以加快这类查询的速度? 看起来COUNT查询最终只能查看每一行,而(不知道更多的细节)我有点惊讶,这个信息不能以某种方式从索引中确定。

UPDATE

使用具有200万行的示例表(具有索引“状态”列),我对GROUP BY查询进行了基准testing。 使用InnoDB存储引擎,查询在我的机器上需要3.0 – 3.2秒。 使用MyISAM,查询需要0.9-1.1秒。 在这两种情况下,计数(*),计数(状态)或计数(1)之间没有显着差异。

MyISAM肯定有点快,但我很好奇,看看有没有办法让等效查询的运行速度更快(例如,10-50毫秒 – 足够快,可以在每个网页请求中调用低stream量站点)没有caching和触发器的精神开销。 这听起来像答案是“没有办法快速运行直接查询”,这是我所期望的 – 我只是想确保我没有错过一个简单的select。

所以问题是

有没有什么技术来加快这种查询?

那么,不是真的。 对于那些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 // 

MyISAM实际上非常快(count)(*),不足之处在于MyISAM存储在数据完整性至关重要的情况下并不可靠并且最好避免。

InnoDB执行count(*)types的查询可能会非常慢,因为它被devise为允许同一个数据的多个并发视图。 所以在任何时候,它都不足以进入指数来计数。

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

数据库以1000条logging开始,我开始一个事务你开始一个事务我删除50条logging你添加50条logging我做了一个COUNT( ),看到950条logging。 你做一个COUNT( )并查看1050条记​​录。 我承诺我的交易 – 数据库现在有950条logging给每个人,但你。 你提交你的事务 – 数据库再次有1000条logging。

InnoDB如何跟上哪些logging对于任何事务是“可见的”或“可修改的”,通过行级locking,事务隔离级别和多版本控制。 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

这就是计算每个人看到的logging数量不是那么直截了当的。

因此,如果您需要频繁且快速地获取这些信息,您需要以某种方式查看caching计数,而不是转到表格。

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

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

build议的解决scheme是:

要快速计数,必须使用自己创build的计数器表,并让应用程序根据插入操作对其进行更新,并将其删除。 如果近似的行数足够,SHOW TABLE STATUS也可以使用。

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

写你自己的解决方法。

这里有很多答案说索引不会帮助,但在我的情况下,它没有…

我的表使用了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; 

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

计数(*),计数(状态)或计数(1)之间没有显着差异

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

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

  • 如果你想要一个“确切”的计数在任何时候都可用,那么你必须通过触发器实时增加和减less,这会减慢你的写入

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

  • 或者你可以解决一个粗略的估计,或一个过时的价值,并使用caching或其他概率的方法。

一般来说,在大于“几”的数值上,没有人对精确的实时计数感兴趣。 无论如何,这是一条红鲱鱼,因为当你阅读它时,价值很可能会发生变化。