MySQL的COUNT()函數理解


MySQL的COUNT()函數理解

標簽(空格分隔): MySQL5.7 COUNT()函數 探討


寫在前面的話

細心的朋友會在平時工作和學習中,可以看到MySQL的COUNT()函數有多種不同的參數,從而會有不同的統計方式,本文正是出於此目的一探究竟。

主要內容&你能獲得什么

辨析COUNT(*)、COUNT(1)、COUNT(0)、COUNT(列名)、COUNT(DISTINCT 列名)的區別和作用。

探討

基本理解

COUNT()函數用來統計表的行數,也就是統計記錄行數,很好理解。查看MySQL5.7官方手冊

官方對COUNT(expr)解釋:

Returns a count of the number of non-NULL values of expr in the rows retrieved by a SELECT statement. The result is a BIGINT value. If there are no matching rows, COUNT() returns 0.

COUNT(*) is somewhat different in that it returns a count of the number of rows retrieved, whether or not they contain NULL values.

COUNT(DISTINCT expr,[expr...])Returns a count of the number of rows with different non-NULL expr values.If there are no matching rows, COUNT(DISTINCT) returns 0.

在SELECT檢索語句中,COUNT(expr)統計並返回參數expr為非NULL值的總行數,COUNT(DISTINCT expr)返回的是參數expr為非NULL值且不相同的總行數,結果是一個BIGINT數據類型的值,占8個字節;如果沒有匹配到滿足條件的行,結果返回0。但是當expr不是具體的列,是COUNT(*)時會統計表中所有的行數,即使某些行是NULL也會被統計在內

測試

新建測試表users

CREATE TABLE `users` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `LoginName` varchar(50) DEFAULT NULL,
  `LoginPwd` varchar(16) DEFAULT NULL,
  `Name` varchar(16) DEFAULT NULL,
  `Address` varchar(16) DEFAULT NULL,
  `Phone` varchar(16) DEFAULT NULL,
  `Mail` varchar(16) DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 

#插入數據
mysql> select * from users;
+----+------------+----------+------+----------+-------------+---------------+
| Id | LoginName  | LoginPwd | Name | Address  | Phone       | Mail          |
+----+------------+----------+------+----------+-------------+---------------+
|  1 | bb1        | 123      | 張三 | 湖北武漢 | 13317190688 | 123@gmail.com |
|  2 | bb3        | 123      | 李四 | 湖北武漢 | 13317190688 | 123@gmail.com |
|  3 | jj4        | 123      | 張三 | 湖北武漢 | 13317190688 | 123@gmail.com |
|  4 | kobeBryant | 123456   | NULL | LA       | NULL        | NULL          |
|  5 | kobe       | 456      | NULL | NULL     | NULL        | NULL          |
|  6 | Jay        | NULL     | NULL | GXI      | NULL        | NULL          |
|  7 | jj4        | NULL     | NULL | NULL     | NULL        | NULL          |
+----+------------+----------+------+----------+-------------+---------------+
7 rows in set

執行查詢

mysql> SELECT COUNT(*),COUNT(1),COUNT(0),COUNT(-1), COUNT(LoginPwd),COUNT(Phone),COUNT(DISTINCT Phone) FROM users;
+----------+----------+----------+-----------+-----------------+--------------+------------------------+
| COUNT(*) | COUNT(1) | COUNT(0) | COUNT(-1) | COUNT(LoginPwd) | COUNT(Phone) | COUNT(DISTINCT Phone) |
+----------+----------+----------+-----------+-----------------+--------------+------------------------+
|        7 |        7 |        7 |         7 |               5 |            3 |                      1 |
+----------+----------+----------+-----------+-----------------+--------------+------------------------+
1 row in set

根據上述結果可以有以下結論:

  • COUNT(*)、COUNT(1)、COUNT(0)統計的是所有行數,結果都是7行。
  • COUNT(LoginPwd)、COUNT(Phone)分別統計列LoginPwd、列Phone的非NULL的行數,結果分別是5行、3行。
  • COUNT(DISTINCT Phone)只統計列Phone的非NULL且不相同的行數,結果是1行。

辨析

對 COUNT(LoginPwd)、COUNT(Phone)和COUNT(DISTINCT Phone)的結果我們不難理解,關鍵是要弄清楚COUNT(*)、COUNT(1)、COUNT(0)這三個式子,它們的使用區別是什么,或者是沒區別。

查看官方文檔:

For MyISAM tables, COUNT(*) is optimized to return very quickly if the SELECT retrieves from one table, no other columns are retrieved, and there is no WHERE clause. 

This optimization only applies to MyISAM tables, because an exact row count is stored for this storage engine and can be accessed very quickly. COUNT(1) is only subject to the same optimization if the first column is defined as NOT NULL.

For transactional storage engines such as InnoDB, storing an exact row count is problematic because multiple transactions may be occurring, each of which may affect the count. 

  • 對於使用MyISAM存儲引擎的每張表都會為了優化查詢,會定義一個變量row count來記錄目前表的總行數,我們可以快速獲得一張表的總行數,但是想要快速拿到這個變量,查詢的時候就要遵循一定的要求,即不能同時查詢其他的列且不能有WHERE語句(如“SELECT COUNT(*) FROM student;”)。如果是使用COUNT(1),這個表的第一列要定義為非NULL才會拿到這個row count變量,否則就是全表掃描統計。
  • 對於事務型存儲引擎,如InnoDB,使用COUNT(*)是全表掃描統計(如果有索引會根據索引優化查詢)。如果有row count這樣的一個變量,因為同一時間可能會有多個事務同時操作,可能會帶來並發操作的問題,row count的結果會不一致,所以事務型引擎並沒有優化COUNT(*)查詢。

COUNT執行細節

  • 執行綜合查詢
mysql> explain SELECT COUNT(*),COUNT(1),COUNT(0),COUNT(-1), COUNT(LoginPwd),COUNT(Phone),COUNT( DISTINCT Phone) FROM users;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | users | ALL  | NULL          | NULL | NULL    | NULL |    7 | NULL  |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set

執行整條語句的時候,可以看到type字段是ALL,使用了全表掃描,表的行數是rows=7

  • 執行COUNT(*)
mysql> explain SELECT COUNT(*) FROM users;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | users | index | NULL          | PRIMARY | 4       | NULL |    7 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set

執行COUNT(*)可以看到type字段是index,沒有使用全表掃描,而是使用了索引優化查詢,使用了主鍵PRIMARY索引,表的行數是rows=7

  • 執行COUNT(1)
mysql> explain SELECT COUNT(1) FROM users;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | users | index | NULL          | PRIMARY | 4       | NULL |    7 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set

執行COUNT(1)可以看到type字段是index,沒有使用全表掃描,而是使用了索引優化查詢,使用了主鍵PRIMARY索引。

  • 執行COUNT(0)
mysql> explain SELECT COUNT(0) FROM users;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | users | index | NULL          | PRIMARY | 4       | NULL |    7 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
1 row in set

執行COUNT(0)可以看到type字段是index,沒有使用全表掃描,而是使用了索引優化查詢,使用了主鍵PRIMARY索引。

對於InnoDB,查詢COUNT(*)和COUNT(1)二者並沒有區別,性能效率等效,都是全表掃描(有索引則會優化自動使用索引)。

InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.

結論

|序號|類別|作用|解釋說明|使用|
|---|---|---|---|
|1|COUNT(*)|統計總行數,含NULL值|MyISAM引擎,如果沒有查詢其他列且無WHERE語句會直接返回row count變量,高效。其他情況全表掃描(有索引則用索引),統計表的總行數。|如果僅僅是統計總行數,任何情況先推薦使用“SELECT COUNT(*) FROM 表名”。|
|2|COUNT(n)|統計總行數,可以是COUNT(任何整數或小數),含NULL值|如COUNT(1),MyISAM引擎如果沒有查詢其他列且無WHERE語句且第一列定義為非NULL會直接返回row count變量,高效。其他情況全表掃描(有索引則用索引)|表的第一列定義為非NULL,推薦使用“SELECT COUNT(1) FROM 表名”。|
|3|COUNT(列名)|統計某一列非NULL的行數|純粹統計指定列的非NULL行數,不區分存儲引擎|看業務需求|
|4|COUNT(DISTINCT 列名)|統計某一列非NULL且不相同的行數|純粹統計指定列的非NULL且不相同的行數,不區分存儲引擎|看業務需求|

使用選擇:

  • COUNT(*)和COUNT(n)本質上一樣,具體響應時間跟存儲引擎和WHERE條件有關。個人習慣使用COUNT(1)。
  • 索引對COUNT()函數很重要,如果要用到索引,MySQL會自動優化使用合適的索引。
  • COUNT(列名)需要注意統計的是非NULL的列。

使用SUM(1)也可以達到統計表總行數的目的,而且也包含NULL值,但是效率沒有COUNT(*)高。

參考:
https://highdb.com/了解-select-count-count1-和-countfield/
官方手冊:https://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_count


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM