目錄
實例
必要知识点
原因分析
執行過程
總結
首頁 資料庫 mysql教程 count(*)為什麼很慢?原因分析

count(*)為什麼很慢?原因分析

Jan 05, 2023 pm 09:21 PM
mysql 後端

count(*)為什麼很慢?以下這篇文章就來跟大家分析一下原因,並聊聊count(*)的執行過程,希望對大家有幫助!

count(*)為什麼很慢?原因分析

本來沒想著寫這篇文章的,因為我覺得這個東西大多數有經驗的開發遇到過,肯定也了解相關的原因,但最近我看到有幾個關注的技術公眾號在推播相關的文章。實在令我吃驚!

先上公眾號文章的結論:

  • count(*) :它會取得所有行的數據,不做任何處理,行數加1。
  • count(1):它會取得所有行的數據,每行固定值1,也是行數加1。
  • count(id):id代表主鍵,它需要從所有行的資料中解析出id字段,其中id肯定都不為NULL,行數加1。
  • count(普通索引列):它需要從所有行的資料中解析出普通索引列,然後判斷是否為NULL,如果不是NULL,則行數 1。
  • count(未加索引列):它會全表掃描取得所有數據,解析中未加索引列,然後判斷是否為NULL,如果不是NULL,則行數 1。

結論:count(*) ≈ count(1) > count(id) > count(普通索引列) > count(未加索引列)

我也不想賣關子了,以上結論純屬放屁。根本就是個人yy出來的東西,甚至不願意去驗證一下,就算看一眼執行計劃,也得不出這麼離譜的結論。

我不敢相信這是一篇被多個技術公眾號轉載的文章!

以下所有的內容都是基於,mysql 5.7 InnoDB引擎, 進行的分析。

拓展:

MyISAM 如果沒有查詢條件,只是簡單的統計表中資料總數,將會傳回的超快,因為service層中取得到表格資訊中的總行數是準確的,而InnoDB只是一個估值。

實例

廢話不多說,先看一個例子。

以下是一張表格資料量有100w,表格中欄位相對較短,整體資料量不算大。

CREATE TABLE `hospital_statistics_data` (
  `pk_id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `id` varchar(36) COLLATE utf8mb4_general_ci NOT NULL COMMENT '外键',
  `hospital_code` varchar(36) COLLATE utf8mb4_general_ci NOT NULL COMMENT '医院编码',
  `biz_type` tinyint NOT NULL COMMENT '1服务流程  2管理效果',
  `item_code` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '考核项目编码',
  `item_name` varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '考核项目名称',
  `item_value` varchar(36) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '考核结果',
  `is_deleted` tinyint DEFAULT NULL COMMENT '是否删除 0否 1是',
  `gmt_created` datetime DEFAULT NULL COMMENT '创建时间',
  `gmt_modified` datetime DEFAULT NULL COMMENT 'gmt_modified',
  `gmt_deleted` datetime(3) DEFAULT '9999-12-31 23:59:59.000' COMMENT '删除时间',
  PRIMARY KEY (`pk_id`)
) DEFAULT CHARSET=utf8mb4  COMMENT='医院统计数据';
登入後複製

此表初始狀態只有一個叢集索引

以下分不同索引情況,來看看COUNT(*)的執行計畫。

1)在只有一個叢集索引的情況下看一下執行計劃。

EXPLAIN select COUNT(*) from hospital_statistics_data;
登入後複製
登入後複製
登入後複製

結果:

關於執行計劃的各個參數的含義,不在本文的討論範圍內,可自行了解。

這裡只關注以下幾個屬性。

  • type: 這裡顯示index,說明使用了索引。

  • key:PRIMARY使用了主鍵索引。

  • key_len: 索引長度8位元組。

這裡有很關鍵的一點:count(*)也會走索引,在目前情況下使用了叢集索引。

好,再往下看。

2)存在一個非聚集索引(二級索引)

為表格新增一個hospital_code索引。

alter table hospital_statistics_data add index idx_hospital_code(hospital_code)
登入後複製

此時表中存在2個索引,主鍵 hospital_code

同樣的,再執行一下:

EXPLAIN select COUNT(*) from hospital_statistics_data;
登入後複製
登入後複製
登入後複製

結果:

同樣的,看一下type、key和key_len三個字段。

是不是覺得有點「神奇」。

為何索引變成剛加入的idx_hospital_code了。

先別急著想結論,再看下面一種情況。

3)存在兩個非聚集索引(二級索引)

在上面的基礎上,再加入一個二級索引。

alter table hospital_statistics_data add index idx_biz_type(biz_type)
登入後複製

此時表中存在3個索引,主鍵 、hospital_code 和 biz_type。

同樣的,執行一下:

EXPLAIN select COUNT(*) from hospital_statistics_data;
登入後複製
登入後複製
登入後複製

結果:

#是不是更困惑了,索引又..又.. .變了.

變成新加入的idx_biz_type。

先不說為何會產生以上的變化,繼續往下分析。

在以上3個索引的基礎上,分別看一下,count(1)count(id)count(index)count(無索引)

這4種情況,與count(*)的執行計畫有何不同。

  • count(1)

  • count(id) 對於範例表來說是,主鍵是pk_id

    #

count(*)為什麼很慢?原因分析

  • count(index)

这里选取biz_type索引字段。

  • count(无索引)

小结:

  • count(index) 会使用当前index指定的索引。

  • count(无索引) 是全表扫描,未走索引。

  • count(1) , count(*), count(id) 一样都会选择idx_biz_type索引

看到这,你还觉得那些千篇一律的公众号文章的结论正确吗?

必要知识点

  • mysql 分为service层引擎层

  • 所有的sql在执行前会经过service层的优化,优化分为很多类型,简单的来说可分为成本规则

  • 执行计划所反映的是service层经过sql优化后,可能的执行过程。并非绝对(免得有些人说我只看执行计划过于片面)。绝大多数情况执行计划是可信的

  • 索引类型分为聚簇索引非聚簇索引(二级索引)。其中数据都是挂在聚簇索引上的,非聚簇索引上只是记录的主键id。

  • 抛开数据内存,只谈数据量,都是扯淡。什么500w就是极限,什么2个表以上的join都需要优化了,什么is null不会走索引等,纯纯的放屁。

  • 相信一点,编写mysql代码的人比,看此文章的大部分人都要优秀。他们会尽可能在执行前,对我这样菜逼写的乱七八糟的sql进行优化。

原因分析

其实原因非常非常简单,上面也说了,service层会基于成本进行优化

并且,正常情况下,非聚簇索引所占有的内存要远远小于聚簇索引。所以问题来了,如果你是mysql的开发人员,你在执行count(*)查询的时候会使用那个索引?

我相信正常人都会使用非聚簇索引

那如果存在2个甚至多个非聚簇索引又该如何选择呢?

那肯定选择最短的,占用内存最小的一个呀,在回头看看上面的实例,还迷惑吗。

同样都是非聚簇索引。idx_hospital_codelen146字节;而idx_biz_typelen只有1。那还要选吗?

那为何count(*)走了索引,却还是很慢呢?

这里要明确一点,索引只是提升效率的一种方式,但不能完全的解决效率问题。count(*)有一个明显的缺陷,就是它要计算总数,那就意味着要遍历所有符合条件的数据,相当于一个计数器,在数据量足够大的情况下,即使使用非聚簇索引也无法优化太多。

官方文档:

InnoDBhandlesSELECT COUNT(*)andSELECT COUNT(1)operations in the same way. There is no performance difference.

简单的来说就是,InnoDB下 count(*) 等价于 count(1)

既然会自动走索引,那么上面那个所谓的速度排序还觉得对吗? count(*)的性能跟数据量有很大的关系,此外最好有一个字段长度较短的二级索引。

拓展:

另外,多说一下,关于网上说的那些索引失效的情况,大多都是片面的,我这里只说一点。量变才能引起质变,索引的失效取决于你圈定数据的范围,若你圈定的数据量占整体数据量的比例过高,则会放弃使用索引,反之则会优先使用索引。但是此规则并不是完美的,有时候可能与你预期的不同,也可以通过一些技巧强制使用索引,但这种方式少用。

举个栗子:

通过上面这个表hospital_statistics_data,我进行了如下查询:

select * from hospital_statistics_data where hospital_code is not null;
登入後複製

此时这个sql会使用到hospital_code的索引吗?

这里也不卖关子了,若hospital_code只有很少一部分数据是null值,那么将不会走索引,反之则走索引。

原因就2个字:回表

好比去買砂糖橘,如果你只買幾斤,那麼你隨便挑籃裡面好的就行。但如果你要買一筐,我相信老闆不會讓你在裡面一個個挑,而是一次給你一整筐,當然大家都不傻,都知道筐裡面一定有那麼幾個壞果子。但是這樣效率最高,對老闆來說損失更小。

執行過程

摘抄自《從根上理解mysql》。我強烈推薦沒有系統學過mysql的,看看這本書。

1.首先在server層維護一個count變數

2.server層向InnoDB引擎要第一筆記錄

3.InnoDB找到第一個二級索引記錄,並回傳至server層(注意:由於此時只是統計記錄數量,所以不需要回表)

4.由於COUNT函數的參數是*,MySQL會將*當作常數0處理。由於0並不是NULL,server層給count變數加1。

5.server層向InnoDB要下一筆記錄。

6.InnoDB透過二級索引記錄的next_record屬性找到下一筆二級索引記錄,並傳回給server層。

7.server層繼續為count變數加1。

8.重複上述過程,直到InnoDB向server層傳回沒記錄可查的訊息。

9.server層將最終的count變數的值傳送到客戶端。

總結

寫完後還是心中挺鬱悶的,現在能從公眾號獲取到的好文章越來越少了,現在已經是知識付費的時代了。

挺懷念剛工作的時候,那時候每天上午都花點時間看看公眾號文章,現在全都是廣告。哎!

不過也正常,誰也不能一直為愛發電。

學習還是建議多看書籍,一般能成書的都不會太差。現在晚上能搜到的都是千篇一律的文章,對錯不知。網路上

【相關推薦:mysql影片教學

以上是count(*)為什麼很慢?原因分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1653
14
CakePHP 教程
1413
52
Laravel 教程
1304
25
PHP教程
1251
29
C# 教程
1224
24
MySQL的角色:Web應用程序中的數據庫 MySQL的角色:Web應用程序中的數據庫 Apr 17, 2025 am 12:23 AM

MySQL在Web應用中的主要作用是存儲和管理數據。 1.MySQL高效處理用戶信息、產品目錄和交易記錄等數據。 2.通過SQL查詢,開發者能從數據庫提取信息生成動態內容。 3.MySQL基於客戶端-服務器模型工作,確保查詢速度可接受。

docker怎麼啟動mysql docker怎麼啟動mysql Apr 15, 2025 pm 12:09 PM

在 Docker 中啟動 MySQL 的過程包含以下步驟:拉取 MySQL 鏡像創建並啟動容器,設置根用戶密碼並映射端口驗證連接創建數據庫和用戶授予對數據庫的所有權限

laravel入門實例 laravel入門實例 Apr 18, 2025 pm 12:45 PM

Laravel 是一款 PHP 框架,用於輕鬆構建 Web 應用程序。它提供一系列強大的功能,包括:安裝: 使用 Composer 全局安裝 Laravel CLI,並在項目目錄中創建應用程序。路由: 在 routes/web.php 中定義 URL 和處理函數之間的關係。視圖: 在 resources/views 中創建視圖以呈現應用程序的界面。數據庫集成: 提供與 MySQL 等數據庫的開箱即用集成,並使用遷移來創建和修改表。模型和控制器: 模型表示數據庫實體,控制器處理 HTTP 請求。

解決數據庫連接問題:使用minii/db庫的實際案例 解決數據庫連接問題:使用minii/db庫的實際案例 Apr 18, 2025 am 07:09 AM

在開發一個小型應用時,我遇到了一個棘手的問題:需要快速集成一個輕量級的數據庫操作庫。嘗試了多個庫後,我發現它們要么功能過多,要么兼容性不佳。最終,我找到了minii/db,這是一個基於Yii2的簡化版本,完美地解決了我的問題。

laravel框架安裝方法 laravel框架安裝方法 Apr 18, 2025 pm 12:54 PM

文章摘要:本文提供了詳細分步說明,指導讀者如何輕鬆安裝 Laravel 框架。 Laravel 是一個功能強大的 PHP 框架,它 упростил 和加快了 web 應用程序的開發過程。本教程涵蓋了從系統要求到配置數據庫和設置路由等各個方面的安裝過程。通過遵循這些步驟,讀者可以快速高效地為他們的 Laravel 項目打下堅實的基礎。

centos7如何安裝mysql centos7如何安裝mysql Apr 14, 2025 pm 08:30 PM

優雅安裝 MySQL 的關鍵在於添加 MySQL 官方倉庫。具體步驟如下:下載 MySQL 官方 GPG 密鑰,防止釣魚攻擊。添加 MySQL 倉庫文件:rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm更新 yum 倉庫緩存:yum update安裝 MySQL:yum install mysql-server啟動 MySQL 服務:systemctl start mysqld設置開機自啟動

MySQL和PhpMyAdmin:核心功能和功能 MySQL和PhpMyAdmin:核心功能和功能 Apr 22, 2025 am 12:12 AM

MySQL和phpMyAdmin是強大的數據庫管理工具。 1)MySQL用於創建數據庫和表、執行DML和SQL查詢。 2)phpMyAdmin提供直觀界面進行數據庫管理、表結構管理、數據操作和用戶權限管理。

centos安裝mysql centos安裝mysql Apr 14, 2025 pm 08:09 PM

在 CentOS 上安裝 MySQL 涉及以下步驟:添加合適的 MySQL yum 源。執行 yum install mysql-server 命令以安裝 MySQL 服務器。使用 mysql_secure_installation 命令進行安全設置,例如設置 root 用戶密碼。根據需要自定義 MySQL 配置文件。調整 MySQL 參數和優化數據庫以提升性能。

See all articles