1. 程式人生 > >MySql的count(*)統計結果很慢?為什麼

MySql的count(*)統計結果很慢?為什麼

MySqlcount統計結果

起因:最近在學習mysql的資料庫,發現在innodb表中大資料量下count(*)的統計結果實在是太慢,所以想找個辦法替代這種查詢,下面分享一下我查詢的過程。

實踐:在給出具體的結論之前,我們先看看下面的現象。

一.建立資料庫

建立資料庫的表語句如下:

create database IF NOT EXISTS MY_TEST default charset utf8COLLATE utf8_general_ci;

二.建立User

建立User表的語句如下,UserId為主鍵id,在Idk上分別建立索引,索引的名字分為了“index_id”和“index_k

”。

create table USER (

UserId bigint(20) unsigned not null auto_increment,

Idint(10) unsigned not null default 0,

kint(10) unsigned not null default 0,

UserName varchar(120) not null default '',

PRIMARY KEY(UserId),

KEY index_Id (Id),

KEY index_k (k)

)Engine=InnoDB DEFAULT CHARSET=UTF8;

檢視User上的索引,查詢結果如下:

mysql> show index from user;

+-------+------------+----------+--------------+-------------+-----------+------

-------+----------+--------+------+------------+---------+

| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardi

nality | Sub_part | Packed | Null | Index_type | Comment |

+-------+------------+----------+--------------+-------------+-----------+------

-------+----------+--------+------+------------+---------+

| user|0 | PRIMARY|1 | UserId| A|4

041613 |NULL | NULL|| BTREE||

| user|1 | index_Id |1 | Id| A|4

041613 |NULL | NULL|| BTREE||

| user|1 | index_k|1 | k| A|4

041613 |NULL | NULL|| BTREE||

+-------+------------+----------+--------------+-------------+-----------+------

-------+----------+--------+------+------------+---------+

3 rows in set (1.30 sec)

從上表中我們可以看到user表上有3個索引,分別為主鍵Primary索引、、二級索引index_Idindex_k

三.User表上比較查詢統計

1.直接count(*)統計

mysql> explain select count(*) from user;

+----+-------------+-------+-------+---------------+----------+---------+------+---------+-------------+

| id | select_type | table | type| possible_keys | key| key_len | ref| rows| Extra|

+----+-------------+-------+-------+---------------+----------+---------+------+---------+-------------+

|1 | SIMPLE| user| index | NULL| index_Id | 4| NULL | 4041613 | Using index |

+----+-------------+-------+-------+---------------+----------+---------+------+---------+-------------+

1 row in set (0.04 sec)

mysql> select count(*) from user;

+----------+

| count(*) |

+----------+

|4058181 |

+----------+

1 row in set (2.50 sec)

在這裡使用selectcount(*) 的時候,預設走的索引是index_Id。雖然user表上有主鍵索引,但是分析引擎計算的結果需要走“index_Id”索引(有的時候走主鍵索引),“4041613”這個數字說明分析引擎認為能夠提取到正確的結果之前需要掃描“4041613”行索引。Rows引數表明該查詢所使用的索引的索引長度,index_Id的索引長度為“4”;

2.主鍵欄位做條件查詢count(*)

這裡我們查詢所有Userid大於0的記錄,我們來看看查詢情況。

mysql> explain select count(*) from user where UserId>0;

+----+-------------+-------+-------+---------------+---------+---------+------+---------+--------------------------+

| id | select_type | table | type| possible_keys | key| key_len | ref| rows| Extra|

+----+-------------+-------+-------+---------------+---------+---------+------+---------+--------------------------+

|1 | SIMPLE| user| range | PRIMARY| PRIMARY | 8| NULL | 2020806 | Using where; Using index |

+----+-------------+-------+-------+---------------+---------+---------+------+---------+--------------------------+

1 row in set (0.13 sec)

mysql> select count(*) from user where UserId>0;

+----------+

| count(*) |

+----------+

|4058181 |

+----------+

1 row in set (15.39 sec)

當我們加上主鍵條件的時候,我們可以看到本次查詢走的索引是“Primary”主鍵索引,我們可以看到此次請求需要15.39秒,比第一個查詢2.50秒慢了很多。主鍵索引的長度為“8”。

3.二級索引做條件查詢count(*)

這裡我們查詢所有Id大於0的記錄,我們來看下查詢結果:

mysql> explain select count(*) from user where id>0;

+----+-------------+-------+-------+---------------+----------+---------+------+---------+--------------------------+

| id | select_type | table | type| possible_keys | key| key_len | ref| rows| Extra|

+----+-------------+-------+-------+---------------+----------+---------+------+---------+--------------------------+

|1 | SIMPLE| user| range | index_Id| index_Id | 4| NULL | 1734104 | Using where; Using index |

+----+-------------+-------+-------+---------------+----------+---------+------+---------+--------------------------+

1 row in set (0.16 sec)

mysql> select count(*) from user where id>0;

+----------+

| count(*) |

+----------+

|4058181 |

+----------+

1 row in set (2.94 sec)

1)和(3)的查詢時間都差不多,基本在2.5秒左右,因為這兩個查詢走的都是“index_Id”索引。但是(2)雖然走的主鍵索引,但是很慢,竟然用掉了15秒,index_Id的索引長度為4,主鍵索引的長度為8,是不是因為索引的長度影響了索引的查詢效率??先別下結論,我們再看下下面的例子。

四.建立AnotherUser

建立另外一張表,這張表與上一張表的區別是主鍵欄位UserIdId欄位型別的調換過來了,主鍵UserIdint(10)Id型別為bigint(20)。建表語句如下:

create table ANOTHERUSER (

UserIdint(10) unsigned not null auto_increment,

Idbigint(20) unsigned not null default 0,

kint(10) unsigned not null default 0,

UserName varchar(120) not null default '',

PRIMARY KEY(UserId),

KEY index_Id (Id),

KEY index_k (k)

)Engine=InnoDB DEFAULT CHARSET=UTF8;

五.AnotherUser表上比較查詢統計

anotherUser表與User表的欄位個數完全相同,唯一不同點在於兩個表的主鍵id的型別不同,另外名稱為“Id”的欄位的型別也不同,兩個表的資料記錄基本相同。

1.直接count(*)統計

mysql> explain select count(*) from anotherUser;

+----+-------------+-------------+-------+---------------+---------+---------+------+---------+-------------+

| id | select_type | table| type| possible_keys | key| key_len | ref| rows| Extra|

+----+-------------+-------------+-------+---------------+---------+---------+------+---------+-------------+

|1 | SIMPLE| anotherUser | index | NULL| PRIMARY | 4| NULL | 4056379 | Using index |

+----+-------------+-------------+-------+---------------+---------+---------+------+---------+-------------+

1 row in set (0.80 sec)

mysql> select count(*) from anotheruser;

+----------+

| count(*) |

+----------+

|4056400 |

+----------+

1 row in set (13.75 sec)

從上面的查詢我們可以看到,count(*)在沒有加任何條件的時候此次查詢走的是主鍵索引,這個表的主鍵索引長度是4。優化器認為在提取到正確結果集之前大概需要掃描“4056379”行索引。

2.主鍵欄位做條件查詢count(*)

我們來看下用主鍵UserId作為查詢條件的情況,下面是查詢的程式碼:

mysql> explain select count(*) from anotherUser where UserId>0;

+----+-------------+-------------+-------+---------------+---------+---------+------+---------+--------------------------+

| id | select_type | table| type| possible_keys | key| key_len | ref| rows| Extra|

+----+-------------+-------------+-------+---------------+---------+---------+------+---------+--------------------------+

|1 | SIMPLE| anotherUser | range | PRIMARY| PRIMARY | 4| NULL | 2028189 | Using where; Using index |

+----+-------------+-------------+-------+---------------+---------+---------+------+---------+--------------------------+

1 row in set (0.04 sec)

mysql> select count(*) from anotherUser where UserId>0;

+----------+

| count(*) |

+----------+

|4056400 |

+----------+

1 row in set (13.82 sec)

我們可以看到,雖然這裡的主鍵索引的長度為4,但是查詢時間基本還是在15秒左右。由此可以看出,index_Id索引查詢時間比主鍵查詢時間短並不是索引長度造成的。

3.二級索引做條件查詢count(*)

我們來測試下走Id索引,anotherUser的表Id欄位是bigInt(20)。查詢結果如下:

mysql> explain select count(*) fromanotherUser where Id>0;

+----+-------------+-------------+-------+---------------+----------+---------+------+---------+--------------------------+

| id | select_type | table| type| possible_keys | key| key_len | ref| rows| Extra|

+----+-------------+-------------+-------+---------------+----------+---------+------+---------+--------------------------+

|1 | SIMPLE| anotherUser | range | index_Id| index_Id | 8| NULL | 1862640 | Using where; Using index |

+----+-------------+-------------+-------+---------------+----------+---------+------+---------+--------------------------+

1 row in set (0.09 sec)

mysql> select count(*) fromanotherUser where Id>0;

+----------+

| count(*) |

+----------+

|4056400 |

+----------+

1 row in set (2.87 sec)

走二級索引index_Id只需要2.5秒左右,該索引的長度是8(比主鍵索引長度4),但是此次統計仍然要比使用主鍵來統計要快的多。這更加說明了兩者的查詢結果不同不是由兩者的索引長度造成的。

六.結論

1.沒有任何條件的查詢不一定走的是主鍵索引,mysql優化器會使用認為是最小代價的索引

2.count(*)的時候,採用主鍵索引比二級索引要慢,而且慢的原因不是因為兩者的索引的長度不同

3.Count(*)在沒有查詢條件的情況下,對innodb引擎的mysql會進行全表掃描,而myasm引擎的mysql無需進行全表掃描,因為myasm的引擎記錄了每個表的多少記錄。但是當有查詢條件的時候,兩者的查詢效率一致。

4.經過後來查詢大量的資料,主鍵索引count(*)的時候之所以慢

lInnoDB引擎

[1]資料檔案和索引檔案儲存在一個檔案中,主鍵索引預設直接指向資料儲存位置。

[2]二級索引儲存指定欄位的索引,實際的指向位置是主鍵索引。當我們通過二級索引統計資料的時候,無需掃描資料檔案;而通過主鍵索引統計資料時,由於主鍵索引與資料檔案存放在一起,所以每次都會掃描資料檔案,所以主鍵索引統計沒有二級索引效率高。

[3]由於主鍵索引直接指向實際資料,所以當我們通過主鍵id查詢資料時要比通過二級索引查詢資料要快。

lMyAsm引擎

[1]該引擎把每個表都分為幾部分儲存,比如使用者表,包含user.frmuser.MYDuser.MYI

[2]User.frm負責儲存表結構

[3]User.MYD負責儲存實際的資料記錄,所有的使用者記錄都儲存在這個檔案中

[4]User.MYI負責儲存使用者表的所有索引,這裡也包括主鍵索引。

5.MyAsm引擎不支援事務處理,沒有仔細深入研究。兩種引擎各有自己的使用場景,每個引擎的特點也不盡相同,感興趣的你可以再仔細深入研究。

本人也是mysql菜鳥,以上是本人的個人觀點,如果有誤,請指正,多謝!