MySql的count(*)統計結果很慢?為什麼
MySql的count統計結果
起因:最近在學習mysql的資料庫,發現在innodb表中大資料量下count(*)的統計結果實在是太慢,所以想找個辦法替代這種查詢,下面分享一下我查詢的過程。
實踐:在給出具體的結論之前,我們先看看下面的現象。
一.建立資料庫
建立資料庫的表語句如下:
create database IF NOT EXISTS MY_TEST default charset utf8COLLATE utf8_general_ci; |
二.建立User表
建立User表的語句如下,UserId為主鍵id,在Id和k上分別建立索引,索引的名字分為了“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_Id和index_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表
建立另外一張表,這張表與上一張表的區別是主鍵欄位UserId和Id欄位型別的調換過來了,主鍵UserId為int(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.frm,user.MYD和user.MYI。
[2]User.frm負責儲存表結構
[3]User.MYD負責儲存實際的資料記錄,所有的使用者記錄都儲存在這個檔案中
[4]User.MYI負責儲存使用者表的所有索引,這裡也包括主鍵索引。
5.MyAsm引擎不支援事務處理,沒有仔細深入研究。兩種引擎各有自己的使用場景,每個引擎的特點也不盡相同,感興趣的你可以再仔細深入研究。
本人也是mysql菜鳥,以上是本人的個人觀點,如果有誤,請指正,多謝!