18 March 2016

刚好看到v2ex一个关于mysql count性能的讨论。

帖子内容如下:

mysql> select count(*) from threads; 

InnoDB ,大约 150 万数据,有主键,六个字段,用时 7s 。我觉得应该能在一秒内完成查询。这个查询会不会临时锁表?因为执行的同时还有另外三个连接在做其它的查询。还是因为阿里云 ECS 的磁盘 IO 太慢?

这还是让我很震惊的,因为mysql到2016年进化到5.7以来,在我的印象中,起码千万级的数据还是可以单机从容处理的。

而楼下的群众的回复更是让我目瞪口呆,有的告诉楼主不要使用count(*).但是这我在《高性能MySQL》中说的完全冲突。

书中原文如下:

我们发现一个常见的错误就是,在括号内指定了一个列却希望统计结果集的行数,如果希望知道的是结果集的行数,最好使用count(*),这样写意义清晰,性能也会很好。

注意最后几个字,并没有提到使用count()会降低性能。所以在innodb表中,使用count()肯定是没有问题的。

公说公有理,婆说婆有理。那么就自己动手测试一下就好了。

这次还准备一共比较一下innodb和myisam的count。

inndo表

Create Table: CREATE TABLE `model_150m` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `title` varchar(200) NOT NULL,
  `content` varchar(200) NOT NULL,
  `create_date` datetime NOT NULL,
  `xx` varchar(200) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1919616 DEFAULT CHARSET=utf8

myisam表

Create Table: CREATE TABLE `model_150m_my` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `create_date` datetime NOT NULL,
  `xx` varchar(200) NOT NULL,
  `title` varchar(200) NOT NULL,
  `content` varchar(200) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=561619 DEFAULT CHARSET=utf8

然后用java插入数据,这里偷懒用了之前一个项目的代码,写了一下model,然后用代码生成器生成了crud的代码,本来打算用junit直接插入了,结果项目还是没有junit,只好写了一个controller来保存数据。

model:

@Table(name="model_150m")
@Entity
public class Model150m {
		private int id;
		private String name;
		private Date create_date;
		private String content;
		private String title;
		private String xx;
        
        /*-- getter & setter -- */
		
		public Model150m() {
			this.name = UUID.randomUUID().toString();
			this.create_date = new Date();
			this.content = UUID.randomUUID().toString();;
			this.title = UUID.randomUUID().toString();;
			this.xx = UUID.randomUUID().toString();;
		}
		

往两个表个插入150w的数据。因为这里我让程序跑着就去睡觉了,一觉睡醒,innodb有191w条,myisam有200w条。

首先是inndo的查询结果。

count(*):

mysql> select count(*) from model_150m;
+----------+
| count(*) |
+----------+
|  1919615 |
+----------+
1 row in set (8.60 sec)

count(1):

mysql> select count(1) from model_150m;
+----------+
| count(1) |
+----------+
|  1919615 |
+----------+
1 row in set (8.81 sec)

count(id):

mysql> select count(id) from model_150m;
+-----------+
| count(id) |
+-----------+
|   1919615 |
+-----------+
1 row in set (8.62 sec)

并没有什么本质区别。。

然后是myisam的count:

mysql> select count(*) from model_150m_my;
+----------+
| count(*) |
+----------+
|  2061618 |
+----------+
1 row in set (0.00 sec)

实际上,对于myisam表,没有where条件的count查询,可以直接从引擎的特性中得到行数。

在mysql的information_schema.tables表中,储存着mysql所有表的数据。

inndo表的保存信息

mysql> select * from information_schema.tables where table_schema  = 'nxspiii' a
nd table_name = 'model_150m'\G
*************************** 1. row ***************************
  TABLE_CATALOG: def
   TABLE_SCHEMA: nxspiii
     TABLE_NAME: model_150m
     TABLE_TYPE: BASE TABLE
         ENGINE: InnoDB
        VERSION: 10
     ROW_FORMAT: Compact
     TABLE_ROWS: 2035929
 AVG_ROW_LENGTH: 220
    DATA_LENGTH: 449691648
MAX_DATA_LENGTH: 0
   INDEX_LENGTH: 0
      DATA_FREE: 1068498944
 AUTO_INCREMENT: 1919616
    CREATE_TIME: 2016-03-17 22:09:09
    UPDATE_TIME: NULL
     CHECK_TIME: NULL
TABLE_COLLATION: utf8_general_ci
       CHECKSUM: NULL
 CREATE_OPTIONS:
  TABLE_COMMENT:
1 row in set (0.10 sec)

可以看到的,inndo引擎的表的table_rows照样有数据,但是错的太离谱了。

myisam表的信息

mysql> select * from information_schema.tables where table_schema  = 'nxspiii' a
nd table_name = 'model_150m_my'\G
*************************** 1. row ***************************
  TABLE_CATALOG: def
   TABLE_SCHEMA: nxspiii
     TABLE_NAME: model_150m_my
     TABLE_TYPE: BASE TABLE
         ENGINE: MyISAM
        VERSION: 10
     ROW_FORMAT: Dynamic
     TABLE_ROWS: 2061618
 AVG_ROW_LENGTH: 96
    DATA_LENGTH: 197915328
MAX_DATA_LENGTH: 281474976710655
   INDEX_LENGTH: 21190656
      DATA_FREE: 0
 AUTO_INCREMENT: 2061619
    CREATE_TIME: 2016-03-18 09:27:14
    UPDATE_TIME: 2016-03-18 10:35:07
     CHECK_TIME: NULL
TABLE_COLLATION: utf8_general_ci
       CHECKSUM: NULL
 CREATE_OPTIONS:
  TABLE_COMMENT:
1 row in set (0.02 sec)

而myisam表的myisam的table_rows就完整准确。

innodb为何这么慢,是因为它支持MVCC,不同的事务能查询到的结果是不一样的,每一次查询都要开一个事务,然后查看这个事务能看到的数据,就导致每一次都是扫描全表。只能去把索引的数量计算一遍,导致了无法避免的消耗。