`
jimmee
  • 浏览: 541700 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

转载[InnoDB系列] -- innodb表如何更快得到count(*)结果

阅读更多

作/译者:叶金荣(imysql#imysql.com>),来源:http://imysql.com,欢迎转载。

起因:在innodb表上做count(*)统计实在是太慢了,因此想办法看能不能再快点。
现象:先来看几个测试案例,如下
一、 sbtest 表上的测试

show create table sbtest\G
*************************** 1. row ***************************
Table: sbtest
Create Table: CREATE TABLE `sbtest` (
`aid` bigint(20) unsigned NOT NULL auto_increment,
`id` int(10) unsigned NOT NULL default '0',
`k` int(10) unsigned NOT NULL default '0',
`c` char(120) NOT NULL default '',
`pad` char(60) NOT NULL default '',
PRIMARY KEY  (`aid`),
KEY `k` (`k`),
KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=latin1

show index from sbtest;
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| sbtest |          0 | PRIMARY  |            1 | aid         | A         |     1000099 |     NULL | NULL   |      | BTREE      |         |
| sbtest |          1 | k        |            1 | k           | A         |          18 |     NULL | NULL   |      | BTREE      |         |
| sbtest |          1 | id       |            1 | id          | A         |     1000099 |     NULL | NULL   |      | BTREE      |         |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

填充了 1000万条 记录。
1、 直接 count(*)

explain SELECT COUNT(*) FROM sbtest;
+----+-------------+--------+-------+---------------+---------+---------+------+---------+-------------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows    | Extra       |
+----+-------------+--------+-------+---------------+---------+---------+------+---------+-------------+
|  1 | SIMPLE      | sbtest | index | NULL          | PRIMARY | 8       | NULL | 1000099 | Using index |
+----+-------------+--------+-------+---------------+---------+---------+------+---------+-------------+
SELECT COUNT(*) FROM sbtest;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (1.42 sec)

可以看到,如果不加任何条件,那么优化器优先采用 primary key 来进行扫描。
2、count(*) 使用 primary key 字段做条件

explain SELECT COUNT(*) FROM sbtest WHERE aid>=0;
+----+-------------+--------+-------+---------------+---------+---------+------+--------+--------------------------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows   | Extra                    |
+----+-------------+--------+-------+---------------+---------+---------+------+--------+--------------------------+
|  1 | SIMPLE      | sbtest | range | PRIMARY       | PRIMARY | 8       | NULL | 485600 | Using where; Using index |
+----+-------------+--------+-------+---------------+---------+---------+------+--------+--------------------------+
SELECT COUNT(*) FROM sbtest WHERE aid>=0;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (1.39 sec)

可以看到,尽管优化器认为只需要扫描 485600 条记录(其实是索引),比刚才少多了,但其实仍然要做全表(索引)扫描。因此耗时和第一种相当。
3、 count(*) 使用 secondary index 字段做条件

explain SELECT COUNT(*) FROM sbtest WHERE id>=0;
+----+-------------+--------+-------+---------------+------+---------+------+--------+--------------------------+
| id | select_type | table  | type  | possible_keys | key  | key_len | ref  | rows   | Extra                    |
+----+-------------+--------+-------+---------------+------+---------+------+--------+--------------------------+
|  1 | SIMPLE      | sbtest | range | id            | id   | 4       | NULL | 500049 | Using where; Using index |
+----+-------------+--------+-------+---------------+------+---------+------+--------+--------------------------+
SELECT COUNT(*) FROM sbtest WHERE id>=0;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.43 sec)

可以看到,采用这种方式查询会非常快。
有人也许会问了,会不会是因为 id 字段的长度比 aid 字段的长度来的小,导致它扫描起来比较快呢?先不着急下结论,咱们来看看下面的测试例子。
二、 sbtest1 表上的测试

show create table sbtest1\G
*************************** 1. row ***************************
Table: sbtest1
Create Table: CREATE TABLE `sbtest1` (
`aid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id` bigint(20) unsigned NOT NULL DEFAULT '0',
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`aid`),
KEY `k` (`k`),
KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=latin1
show index from sbtest1;
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table   | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| sbtest1 |          0 | PRIMARY  |            1 | aid         | A         |     1000099 |     NULL | NULL   |      | BTREE      |         |
| sbtest1 |          1 | k        |            1 | k           | A         |          18 |     NULL | NULL   |      | BTREE      |         |
| sbtest1 |          1 | id       |            1 | id          | A         |     1000099 |     NULL | NULL   |      | BTREE      |         |
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

这个表里,把 aid 和 id 的字段长度调换了一下,也填充了 1000万条 记录。
1、 直接 count(*)

explain SELECT COUNT(*) FROM sbtest1;
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref  | rows    | Extra       |
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------------+
|  1 | SIMPLE      | sbtest1 | index | NULL          | PRIMARY | 4       | NULL | 1000099 | Using index |
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------------+
SELECT COUNT(*) FROM sbtest1;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (1.42 sec)

可以看到,如果不加任何条件,那么优化器优先采用 primary key 来进行扫描。
2、count(*) 使用 primary key 字段做条件

explain SELECT COUNT(*) FROM sbtest1 WHERE aid>=0;
+----+-------------+---------+-------+---------------+---------+---------+------+--------+--------------------------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref  | rows   | Extra                    |
+----+-------------+---------+-------+---------------+---------+---------+------+--------+--------------------------+
|  1 | SIMPLE      | sbtest1 | range | PRIMARY       | PRIMARY | 4       | NULL | 316200 | Using where; Using index |
+----+-------------+---------+-------+---------------+---------+---------+------+--------+--------------------------+
1 row in set (0.00 sec)
SELECT COUNT(*) FROM sbtest1 WHERE aid>=0;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (1.42 sec)

可以看到,尽管优化器认为只需要扫描 485600 条记录(其实是索引),比刚才少多了,但其实仍然要做全表(索引)扫描。因此耗时和第一种相当。
3、 count(*) 使用 secondary index 字段做条件

explain SELECT COUNT(*) FROM sbtest1 WHERE id>=0;
+----+-------------+---------+-------+---------------+------+---------+------+--------+--------------------------+
| id | select_type | table   | type  | possible_keys | key  | key_len | ref  | rows   | Extra                    |
+----+-------------+---------+-------+---------------+------+---------+------+--------+--------------------------+
|  1 | SIMPLE      | sbtest1 | range | id            | id   | 8       | NULL | 500049 | Using where; Using index |
+----+-------------+---------+-------+---------------+------+---------+------+--------+--------------------------+
1 row in set (0.00 sec)
SELECT COUNT(*) FROM sbtest1 WHERE id>=0;
+----------+
| COUNT(*) |
+----------+
|  1000000 |
+----------+
1 row in set (0.45 sec)

可以看到,采用这种方式查询会非常快。
上面的所有测试,均在 mysql 5.1.24 环境下通过,并且每次查询前都重启了 mysqld。
可以看到,把 aid 和 id 的长度调换之后,采用 secondary index 查询仍然是要比用 primary key 查询来的快很多。看来主要不是字段长度引起的索引扫描快慢,而是采用 primary key 以及 secondary index 引起的区别。那么,为什么用 secondary index 扫描反而比 primary key 扫描来的要快呢?我们就需要了解innodb的 clustered index 和secondary index 之间的区别了。
innodb 的 clustered index 是把 primary key 以及 row data 保存在一起的,而 secondary index 则是单独存放,然后有个指针指向 primary key。因此,需要进行 count(*) 统计表记录总数时,利用 secondary index 扫描起来,显然更快。而primary key则主要在扫描索引,同时要返回结果记录时的作用较大,例如:

SELECT * FROM sbtest WHERE aid = xxx;

那既然是使用 secondary index 会比 primary key 更快,为何优化器却优先选择 primary key 来扫描呢,Heikki Tuuri 的回答是:

in the example table, the secondary index is inserted into in a perfect order! That is
very unusual. Normally the secondary index would be fragmented, causing random disk I/O,
and the scan would be slower than in the primary index.
I am changing this to a feature request: keep 'clustering ratio' statistics on a secondary
index and do the scan there if the order is almost the same as in the primary index. I
doubt this feature will ever be implemented, though.

详情请看:这个 bug,以及这个文章:InnoDB Row Counting using Indexes
最后感谢老杨的帮助。

分享到:
评论

相关推荐

    数据库引擎 MyISAM 和 InnoDB 对比

    - **InnoDB**则在复杂的多表联接和涉及大量更新的操作中表现更佳。 7. **备份与恢复**: - **MyISAM**可以简单地通过复制文件进行备份。 - **InnoDB**支持在线热备份,并且提供了更完善的恢复机制。 8. **插入...

    cacti-mysql-template指标解释.pdf

    - **InnoDB Internal Hash Memory Usage**:监控InnoDB内部哈希表的内存使用情况。 - **InnoDB I/O Activity**:展示InnoDB的I/O活动情况,包括读写操作的数量。 - **InnoDB I/O Pending**:显示正在等待处理的I/O...

    mysql5.1动态修改的参数变量

    - **说明**:InnoDB 表空间自动扩展增量。 31. **`innodb_concurrency_tickets`** - **值类型**:`numeric` - **类型**:`GLOBAL` - **说明**:InnoDB 并发控制机制中的票数。 32. **`innodb_max_dirty_pages_...

    myisam innodb对比1

    - 对于无条件的COUNT(*),因为维护了一个行数的计数器,可以直接返回结果。 - 有条件查询时,与InnoDB相同,都需要全表扫描。 **InnoDB**: - 不保存具体的行数,COUNT(*)需要全表扫描。 - 有条件查询时,同样需要...

    mysql 学习笔记

    - `DELETE`操作时,InnoDB不重新构建表而是逐行删除,对于清空大型表推荐使用`TRUNCATE TABLE`命令。 #### 十一、外键支持 - **MyISAM**: - 不支持外键约束。 - **InnoDB**: - 支持外键约束,这对于维护数据...

    MySql培训日志_stu

    - 事务是作为单个逻辑工作单元执行的一系列操作。 - **1.16.2 事务的提交与回滚演示** - 使用`START TRANSACTION;`开始一个事务,`COMMIT;`提交事务,`ROLLBACK;`回滚事务。 - **1.16.3 自动提交模式** - 默认...

    MySQL常见面试题(表连接类型,count(*),count(列),count(1)的区别,索引,存储引擎,锁,优化)

    但在某些场景下,如统计非空特定列,COUNT(列)会更快,因为它仅计算特定列的非NULL值。 3. **索引** - **聚集索引(Clustered Index)**:索引和数据行存储在一起,索引的顺序决定了数据行的物理存储顺序。主键...

    Mysql innodb tablespace 表空间实践

    ### MySQL Innodb Tablespace 表空间实践 #### 知识点概述 - **表空间(Tablespace)**:在MySQL的InnoDB存储引擎中,表空间是用来管理数据存储的一种方式。它提供了对物理磁盘上数据文件组织的一种逻辑视图。 - *...

    Innodb与Myisam引擎的区别与应用场景

    - **InnoDB**:不维护这样的计数器,在执行COUNT(*)查询时需要扫描整个表来计算结果。这可能导致在大型表上的性能下降。 5. **索引类型** - **MyISAM**:支持全文索引,这是InnoDB所不具备的功能之一。对于需要...

    mysql学习笔记(无水印版)

    - 将已存在的表从 **MyISAM** 改为 **InnoDB** 或反之。 - **不同存储引擎的特点对比**: - 比较 **InnoDB** 和 **MyISAM** 在性能、功能等方面的差异。 #### 5. 数据操作语言(DML) - **DELETE**:删除表中的...

    MySQL基本知识点思维导图

    - **定义**: 视图是基于SQL语句的结果集而建立的虚拟表。 - **创建视图**: `CREATE VIEW view_name AS SELECT ...;` - **删除视图**: `DROP VIEW view_name;` #### 二、MySQL核心查询 **2.1 简单查询** - **...

    当学习MySQL时,一个清晰的学习路线可以帮助你更系统地掌握知识和技能

    - 聚合函数: `COUNT`, `SUM`, `AVG`, `MAX`, `MIN` 等。 - 分组: 使用 `GROUP BY` 对结果集进行分组。 - 排序: 使用 `ORDER BY` 对结果集排序。 **3. SQL 优化技巧** - **使用索引**: - 为频繁使用的字段创建...

    mysql练习复习最佳帮手

    ### MySQL练习复习最佳帮手知识点解析 #### 一、数据库概述 **1.1 数据存储方式** - **文件系统**:传统的数据管理方法之一,通过操作系统提供的...通过实践这些知识点,可以帮助你更好地理解和掌握MySQL的使用技巧。

    数据库实战.doc数据库实战.doc

    - 存储引擎:InnoDB支持事务处理,MyISAM适用于读取密集型应用。 **注意事项** - **初始化设置**:如字符集、默认存储引擎等。 - **安全性配置**:设置强密码策略,限制网络访问权限。 #### 三、数据查询 **基本...

    MySQL开发教程.docx

    ### MySQL开发教程知识点详解 #### 一、数据库基础知识 **概念与分类:** ...通过深入学习这些知识点并结合具体的案例和项目实践,可以帮助开发者更好地理解和运用 MySQL 数据库,从而提高开发效率和应用性能。

    JMFS-Interview-Notebook-MySQL-master.zip

    - **聚合函数**:COUNT、SUM、AVG、MAX和MIN等用于统计和计算。 5. **索引** - **索引的作用**:提高查询性能,通过预排序的数据指针快速定位到所需记录。 - **B-Tree索引**:最常见的索引类型,适用于范围查询...

    100道MySQL数据库经典面试题

    - **COUNT(*)性能**:MyISAM更快,因保存了总行数,InnoDB需全表扫描。 - **全文索引**:MyISAM支持,InnoDB5.7以后也支持。 - **锁定级别**:InnoDB支持行级锁,MyISAM支持表级锁。 - **主键**:InnoDB必须有...

    InnoDB5项最佳实践

    ### InnoDB最佳实践详解 #### 一、关于`COUNT(*)` **知识点:** - MyISAM存储引擎直接存储总行数,因此对于`SELECT COUNT(*)`这类查询操作能够迅速返回结果。 - InnoDB则需要逐行扫描计算总数,因此对于大规模数据...

    《MySQL数据库开发》期末复习题.docx(共15页)

    根据提供的文档信息,我们可以整理出一系列关于MySQL数据库开发的关键知识点,这些知识点主要涵盖了数据库的基本概念、数据类型、存储过程、锁定机制、事务处理等方面。下面将对这些知识点进行详细的阐述。 ### ...

    mysql军规-sql规范

    - **建议**:对于涉及复杂计算的需求,如统计分析等,应考虑使用后端编程语言(如Java、Python等)实现,并将结果存储回数据库。 **2. 控制单表数据量,单表记录控制在千万级** - **解释**:单个表的数据量过大...

Global site tag (gtag.js) - Google Analytics