在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或其他概率的方法。
一般来说,在大于“几”的数值上,没有人对精确的实时计数感兴趣。 无论如何,这是一条红鲱鱼,因为当你阅读它时,价值很可能会发生变化。