count(*),count(1),count(字段)的對比

Oracle:

參考 https://cloud.tencent.com/developer/article/1388976的方法,使用10053進行分析。
使用的版本爲Oracle 11.2.0.4

SQL> alter session set events='10053 trace name context forever,level 2';

會話已更改。

SQL> create table test1012(id1 number primary key,id2 number,id3 number);

表已創建。

SQL> create unique index idx_1012_3 on test1012(id3);

索引已創建。

SQL> declare
  2  begin
  3    for i in 1 .. 10000 loop
  4      insert into test1012 values (i, i + 1, i + 2);
  5    end loop;
  6    commit;
  7  end;
  8  /

PL/SQL 過程已成功完成。

SQL> select count(*) from test.test1012;

  COUNT(*)
----------
     10000

SQL> select count(1) from test.test1012;

  COUNT(1)
----------
     10000

SQL> select count(id1) from test.test1012;

COUNT(ID1)
----------
     10000

SQL> select count(id2) from test.test1012;

COUNT(ID2)
----------
      9999

SQL> select count(id3) from test.test1012;

COUNT(ID3)
----------
     10000

然後去看看trace文件(只截取部分) 

*******************************************
Peeked values of the binds in SQL statement
*******************************************

Final query after transformations:******* UNPARSED QUERY IS *******
SELECT COUNT(*) "COUNT(*)" FROM "TEST"."TEST1012" "TEST1012"
kkoqbc: optimizing query block SEL$1 (#0)
        
        :
    call(in-use=1216, alloc=16344), compile(in-use=56264, alloc=58632), execution(in-use=2464, alloc=4032)

kkoqbc-subheap (create addr=0x0000000000E7FA10)
============
Plan Table
============
---------------------------------------------+-----------------------------------+
| Id  | Operation              | Name        | Rows  | Bytes | Cost  | Time      |
---------------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT       |             |       |       |     7 |           |
| 1   |  SORT AGGREGATE        |             |     1 |       |       |           |
| 2   |   INDEX FAST FULL SCAN | SYS_C0011149|   10K |       |     7 |  00:00:01 |
---------------------------------------------+-----------------------------------+


CNT:   Considering count(col) to count(*) on query block SEL$1 (#0)
*************************
Count(col) to Count(*) (CNT)
*************************
CNT:     COUNT() to COUNT(*) done.
query block SEL$1 (#0) unchanged
Considering Query Transformations on query block SEL$1 (#0)
*******************************************
Peeked values of the binds in SQL statement
*******************************************

Final query after transformations:******* UNPARSED QUERY IS *******
SELECT COUNT(*) "COUNT(1)" FROM "TEST"."TEST1012" "TEST1012"
kkoqbc: optimizing query block SEL$1 (#0)
        
        :
    call(in-use=1240, alloc=16344), compile(in-use=56384, alloc=58632), execution(in-use=2464, alloc=4032)

kkoqbc-subheap (create addr=0x0000000000E7FA10)
============
Plan Table
============
---------------------------------------------+-----------------------------------+
| Id  | Operation              | Name        | Rows  | Bytes | Cost  | Time      |
---------------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT       |             |       |       |     7 |           |
| 1   |  SORT AGGREGATE        |             |     1 |       |       |           |
| 2   |   INDEX FAST FULL SCAN | SYS_C0011149|   10K |       |     7 |  00:00:01 |
---------------------------------------------+-----------------------------------+
** Using dynamic sampling card. : 10000
** Dynamic sampling updated table card.
  Table: TEST1012  Alias: TEST1012
    Card: Original: 10000.000000  Rounded: 10000  Computed: 10000.00  Non Adjusted: 10000.00
  Access Path: TableScan
    Cost:  9.05  Resp: 9.05  Degree: 0
      Cost_io: 9.00  Cost_cpu: 1699400
      Resp_io: 9.00  Resp_cpu: 1699400
  Access Path: index (index (FFS))
    Index: SYS_C0011149
    resc_io: 7.00  resc_cpu: 442429
    ix_sel: 0.000000  ix_sel_with_filters: 1.000000 
  Access Path: index (FFS)
    Cost:  7.01  Resp: 7.01  Degree: 1
      Cost_io: 7.00  Cost_cpu: 442429
      Resp_io: 7.00  Resp_cpu: 442429
  Access Path: index (FullScan)
    Index: SYS_C0011149
    resc_io: 21.00  resc_cpu: 649550
    ix_sel: 1.000000  ix_sel_with_filters: 1.000000 
    Cost: 21.02  Resp: 21.02  Degree: 1
******** Begin index join costing ********
  ****** trying bitmap/domain indexes ******
  Access Path: index (FullScan)
    Index: SYS_C0011149
    resc_io: 21.00  resc_cpu: 649550
    ix_sel: 1.000000  ix_sel_with_filters: 1.000000 
    Cost: 21.02  Resp: 21.02  Degree: 0
  Access Path: index (FullScan)
    Index: SYS_C0011149
    resc_io: 21.00  resc_cpu: 649550
    ix_sel: 1.000000  ix_sel_with_filters: 1.000000 
    Cost: 21.02  Resp: 21.02  Degree: 0
  Bitmap nodes:
    Used SYS_C0011149
      Cost = 26.273311, sel = 1.000000
  ****** finished trying bitmap/domain indexes ******
******** End index join costing ********
  Best:: AccessPath: IndexFFS
  Index: SYS_C0011149
         Cost: 7.01  Degree: 1  Resp: 7.01  Card: 10000.00  Bytes: 0

***************************************


CNT:   Considering count(col) to count(*) on query block SEL$1 (#0)
*************************
Count(col) to Count(*) (CNT)
*************************
CNT:     Converting COUNT(ID1) to COUNT(*).
CNT:     COUNT() to COUNT(*) done.
query block SEL$1 (#0) unchanged
*******************************************
Peeked values of the binds in SQL statement
*******************************************

Final query after transformations:******* UNPARSED QUERY IS *******
SELECT COUNT(*) "COUNT(ID1)" FROM "TEST"."TEST1012" "TEST1012"
kkoqbc: optimizing query block SEL$1 (#0)
        
        :
    call(in-use=1240, alloc=16344), compile(in-use=56752, alloc=58632), execution(in-use=2464, alloc=4032)

kkoqbc-subheap (create addr=0x0000000000E7FA10)
============
Plan Table
============
---------------------------------------------+-----------------------------------+
| Id  | Operation              | Name        | Rows  | Bytes | Cost  | Time      |
---------------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT       |             |       |       |     7 |           |
| 1   |  SORT AGGREGATE        |             |     1 |       |       |           |
| 2   |   INDEX FAST FULL SCAN | SYS_C0011149|   10K |       |     7 |  00:00:01 |
---------------------------------------------+-----------------------------------+



CNT:   Considering count(col) to count(*) on query block SEL$1 (#0)
*************************
Count(col) to Count(*) (CNT)
*************************
CNT:     COUNT() to COUNT(*) not done.
query block SEL$1 (#0) unchanged
*******************************************
Peeked values of the binds in SQL statement
*******************************************

Final query after transformations:******* UNPARSED QUERY IS *******
SELECT COUNT("TEST1012"."ID2") "COUNT(ID2)" FROM "TEST"."TEST1012" "TEST1012"
kkoqbc: optimizing query block SEL$1 (#0)
        
        :
    call(in-use=1240, alloc=16344), compile(in-use=56752, alloc=58632), execution(in-use=2464, alloc=4032)

kkoqbc-subheap (create addr=0x0000000000E7FA10)
============
Plan Table
============
--------------------------------------+-----------------------------------+
| Id  | Operation           | Name    | Rows  | Bytes | Cost  | Time      |
--------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT    |         |       |       |     9 |           |
| 1   |  SORT AGGREGATE     |         |     1 |    13 |       |           |
| 2   |   TABLE ACCESS FULL | TEST1012|   10K |  127K |     9 |  00:00:01 |
--------------------------------------+-----------------------------------+


CNT:   Considering count(col) to count(*) on query block SEL$1 (#0)
*************************
Count(col) to Count(*) (CNT)
*************************
CNT:     COUNT() to COUNT(*) not done.
query block SEL$1 (#0) unchanged
*******************************************
Peeked values of the binds in SQL statement
*******************************************

Final query after transformations:******* UNPARSED QUERY IS *******
SELECT COUNT("TEST1012"."ID3") "COUNT(ID3)" FROM "TEST"."TEST1012" "TEST1012"
kkoqbc: optimizing query block SEL$1 (#0)
        
        :
    call(in-use=1240, alloc=16344), compile(in-use=56752, alloc=58632), execution(in-use=2464, alloc=4032)

kkoqbc-subheap (create addr=0x0000000000E7FA10)
============
Plan Table
============
-------------------------------------------+-----------------------------------+
| Id  | Operation              | Name      | Rows  | Bytes | Cost  | Time      |
-------------------------------------------+-----------------------------------+
| 0   | SELECT STATEMENT       |           |       |       |     7 |           |
| 1   |  SORT AGGREGATE        |           |     1 |    13 |       |           |
| 2   |   INDEX FAST FULL SCAN | IDX_1012_3|   10K |  127K |     7 |  00:00:01 |
-------------------------------------------+-----------------------------------+

       我們可以發現,每一步都有一個 CNT:   Considering count(col) to count(*),是不是Oracle在做這種count()聚合查詢時,都在嘗試能不能轉換成count(*),如果可以的話就直接將sql進行轉換,例如:

SELECT COUNT(*) "COUNT(1)" FROM "TEST"."TEST1012" "TEST1012"

       看似count(1),實則count(*)。理所當然的,他們的執行計劃也是一樣的,都是對主鍵的index fast full scan

       所以如果只是爲了統計行數,我們可以認爲count(*)=count(1)=count(主鍵)
       count(字段)的話,更多的是業務上的需求(去掉null值的統計),因爲有個篩選的過程,所以相比來說肯定是要慢點。

Mysql:

       我們這裏就只討論InnoDB了。我的Mysql版本是5.7.19。
       和Oracle的區別是,InnoDB的表是IOT,所以對於Mysql來說,二級索引上的信息也足夠我們進行count()的,在count(1)和count(*)時,Mysql會選取它認爲最快的遍歷方式,比如我這實驗:

mysql> show create table emp3;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                    |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| emp3  | CREATE TABLE `emp3` (
  `id` int(10) NOT NULL,
  `name` varchar(25) DEFAULT NULL,
  `deptid` int(11) DEFAULT NULL,
  `salary` float DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_emp3_salary` (`salary`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.02 sec)

mysql> desc select count(id) from emp3;
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key             | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | emp3  | NULL       | index | NULL          | idx_emp3_salary | 5       | NULL | 997998 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.01 sec)

mysql> desc select count(*) from emp3;
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key             | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | emp3  | NULL       | index | NULL          | idx_emp3_salary | 5       | NULL | 997998 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.01 sec)

mysql> desc select count(1) from emp3;
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key             | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | emp3  | NULL       | index | NULL          | idx_emp3_salary | 5       | NULL | 997998 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> desc select count(name) from emp3;
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------+
|  1 | SIMPLE      | emp3  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 997998 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> desc select count(salary) from emp3;
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key             | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | emp3  | NULL       | index | NULL          | idx_emp3_salary | 5       | NULL | 997998 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-----------------+---------+------+--------+----------+-------------+

       可以看到,Mysql選擇了遍歷idx_emp3_salary這個二級索引。
       對於InnoDB來說,在遍歷之後,InnoDB會把結果取出來,返回給Server層,然後由Server層進行非空判斷並累加。
       但是 count(*) 是例外,並不會把全部字段取出來,而是專門做了優化,不取值。
       (參考:https://time.geekbang.org/column/article/72775)

       從官方文檔上我們也可以看到:
       Prior to MySQL 5.7.18, InnoDB processes SELECT COUNT(*) statements by scanning the clustered index. As of MySQL 5.7.18, InnoDB processes SELECT COUNT(*) statements by traversing the smallest available secondary index unless an index or optimizer hint directs the optimizer to use a different index. If a secondary index is not present, the clustered index is scanned.
       InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.

       所以對於InnoDB,我們可以認爲count(*)≈count(1)>count(主鍵)>count(字段)

 

       

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章