MySQL的简单COUNT查询(无WHERE子句)

时间:2009-03-31 23:56:00 来源:互联网 作者: 神秘的大神 字体:

    在SQL查询中一个比较常见查询应当是COUNT操作。对于带WHERE子句的查询没太多可说的,有索引就用索引,没有索引——嘿嘿,累坏机器啊。
    而不带WHERE子句简单COUNT查询的,对于不同存储引擎则有一些不同。

 

     假设一个需求
    我们有一个表存放了大量用户的信息。某一个页面需要显示用户数据列表。我们自然不能一下子把所有数据一次性SELECT出来放到页面上,因此需要支持翻页,比如每页只显示100个。当然这个很容易通过LIMIT实现。但是,页面支持翻页以后,就引出另一个需求,我们需要显示当前是第几页以及一共有多少页。这就需要知道当前用户的总数,我们也许就需要执行这样一个简单的COUNT查询SQL:
SELECT COUNT(*) FROM t_userinfo

 

    简单COUNT查询在不同存储引擎上的对比

    最初的想法是:对于这种没有WHERE子句的COUNT查询,MySQL应当可以迅速返回一个值,而无须去数一共多少行。
    在两种引擎上EXPLAIN以后,发现不完全如此。
    对于InnoDB引擎,EXPLAIN结果如下

mysql> EXPLAIN SELECT COUNT(*) FROM t_userinfo;
+----+-------------+------------------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table            | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+------------------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | t_userinfo       | index | NULL          | PRIMARY |       4 | NULL |  481 | Using index |
+----+-------------+------------------+-------+---------------+---------+---------+------+------+-------------+

    很明显,因为t_userinfo里面有索引,所以InnoDB使用了索引,但即使如此,还是遍历了索引以后才返回结果。

    对于MyISAM引擎,EXPLAIN结果如下

mysql> EXPLAIN SELECT COUNT(*) FROM t_userinfo;
+----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                        |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+
|  1 | SIMPLE      | NULL  | NULL | NULL          | NULL |    NULL | NULL | NULL | Select tables optimized away |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+

     这个输出的结果里略微有点奇怪,Extra列输出了"Select tables optimized away"语句。这个在MySQL的手册里面没有任何提及,不过看其他各列的数据大概能猜到意思:SELECT操作已经优化到不能再优化了(MySQL根本没有遍历表或索引就返回数据了)。

    在MySQL官方站点翻到两段相关的描述,印证了上述观点。原文如下:

    For explains on simple count queries (i.e. explain select count(*) from people) the extra section will read "Select tables optimized away." This is due to the fact that MySQL can read the result directly from the table internals and therefore does not need to perform the select.

     …… ……

    SELECT count(*) is so common so it is partly optimized away.
    If you are using MyISAM or HEAP tables it reads the information directly from the table information which is lightning fast and is actually the information that show table status displays.
    But for InnoDB tables it actually has to perform an index scan on the primary index which can take a while depending on size of table, innodb_buffer_size, hardware etc.

 

    相关建议

    那么,如果你的COUNT查询只打算在MyISAM或HEAP引擎上执行,直接使用上面的语句没有性能问题,无需特殊的优化。而如果在InnoDB上执行,或者你不确定以后会不会在InnoDB上执行的话,也许需要考虑一下其中的性能问题。

    如果不是要求很精确的统计(比如本文最初提出的需求),一个建议是使用如下的语句

SHOW TABLE STATUS LIKE 't_userinfo';

    对于MyISAM或者HEAP引擎,该查询返回的行数是准确值;对于InnoDB,该查询返回的行数是一个近似值(事实上,对于InnoDB,该查询返回的各字段值绝大多数是近似值)。

    对于某个案例,InnoDB引擎下SHOW TABLE STATUS查询返回行数是519;而SELECT COUNT(*)返回行数是476。由于该表中有一个自增字段Fid。SELECT MAX(Fid)返回的结果是488。
    对于另一个示例,InnoDB引擎下SHOW TABLE STATUS查询返回行数是487;而SELECT COUNT(*)返回行数是480。由于该表中有一个自增字段Fid。SELECT MAX(Fid)返回的结果是490。
    可以看到,基本上这个值是很接近的。想了解为什么产生这样的近似值,可以阅读InnoDB相关文档。

    当然,由于"SHOW TABLE STATUS"语句是MySQL特有的语句,而不是标准SQL语句。出于某些考量,这个方案无法接受,那么为了性能另一个建议是建立一个计数表,存放各种COUNT计数。
    一个较通用的计数表字段如下:

{table_name, where_clause, group_clause, count}