MySQL的count()方法慢

前言

mysql用count方法查全表數據,在不同的存儲引擎里實現不同,myisam有專門字段記錄全表的行數,直接讀這個字段就好了。而innodb則需要一行行去算。

比如說,你有一張短信表(sms),里面放了各種需要發送的短信信息。

sms建表sql:

sms表;

需要注意的是state字段,為0的時候說明這時候短信還未發送。

此時還會有一個異步線程不斷的撈起未發送(state=0)的短信數據,執行發短信操作,發送成功之后state字段會被置為1(已發送)。也就是說未發送的數據會不斷變少。

異步線程發送短信:

假設由于某些原因,你現在需要做一些監控,比如監控的內容是,你的sms數據表里還有沒有state=0(未發送)的短信,方便判斷一下堆積的未發送短信大概在什么樣的一個量級。

為了獲取滿足某些條件的行數是多少,我們一般會使用count()方法。

這時候為了獲取未發送的短信數據,我們很自然就想到了使用下面的sql語句進行查詢。

1

select count(*) from sms where state = 0;

然后再把獲得數據作為打點發給監控服務。

當數據表小的時候,這是沒問題的,但當數據量大的時候,比如未發送的短信到了百萬量級的時候,你就會發現,上面的sql查詢時間會變得很長,最后timeout報錯,查不出結果了。

為什么?

我們先從count()方法的原理聊起。

count()的原理

count()方法的目的是計算當前sql語句查詢得到的非NULL的行數。

我們知道mysql是分為server層和存儲引擎層的。

Mysql架構:

存儲引擎層里可以選擇各種引擎進行存儲,最常見的是innodb、myisam。具體使用哪個存儲引擎,可以通過建表sql里的ENGINE?字段進行指定。比如這篇文章開頭的建表sql里用了ENGINE=InnoDB,那這張表用的就是innodb引擎。

雖然在server層都叫count()方法,但在不同的存儲引擎下,它們的實現方式是有區別的。

比如同樣是讀全表數據??select count(*) from sms;語句。

使用?myisam引擎的數據表里有個記錄當前表里有幾行數據的字段,直接讀這個字段返回就好了,因此速度快得飛起。

而使用innodb引擎的數據表,則會選擇體積最小的索引樹,然后通過遍歷葉子節點的個數挨個加起來,這樣也能得到全表數據。

因此回到文章開頭的問題里,當數據表行數變大后,單次count就需要掃描大量的數據,因此很可能就會出現超時報錯。

那么問題就來了。

為什么innodb不能像myisam那樣實現count()方法

myisam和innodb這兩個引擎,有幾個比較明顯的區別,這個是八股文常考了。

其中最大的區別在于myisam不支持事務,而innodb支持事務。

而事務,有四層隔離級別,其中默認隔離級別就是可重復讀隔離級別(RR)。

四層隔離級別:

innodb引擎通過MVCC實現了可重復隔離級別,事務開啟后,多次執行同樣的select快照讀,要能讀到同樣的數據。

于是我們看個例子:為什么innodb不單獨記錄表行數?

對于兩個事務A和B,一開始sms表假設就2條數據,那事務A一開始確實是讀到2條數據。事務B在這期間插入了1條數據,按道理數據庫其實有3條數據了,但由于可重復讀的隔離級別,事務A依然還是只能讀到2條數據。

因此由于事務隔離級別的存在,不同的事務在同一時間下,看到的表內數據行數是不一致的,因此innodb,沒辦法,也沒必要像myisam那樣單純的加個count字段信息在數據表上。

那如果不可避免要使用count(),有沒有辦法讓它快一點?

各種count()方法的原理

count()的括號里,可以放各種奇奇怪怪的東西,想必大家應該看過,比如放個星號*,放個1,放個索引列啥的。

我們來分析下他們的執行流程。

count方法的大原則是server層會從innodb存儲引擎里讀來一行行數據,并且只累計非null的值。但這個過程,根據count()方法括號內的傳參,有略有不同。

count(*):server層拿到innodb返回的行數據,不對里面的行數據做任何解析和判斷,默認取出的值肯定都不是null,直接行數+1。

count(1):server層拿到innodb返回的行數據,每行放個1進去,默認不可能為null,直接行數+1.

count(某個列字段):由于指明了要count某個字段,innodb在取數據的時候,會把這個字段解析出來返回給server層,所以會比count(1)和count(*)多了個解析字段出來的流程。

如果這個列字段是主鍵id,主鍵是不可能為null的,所以server層也不用判斷是否為null,innodb每返回一行,行數結果就+1.

如果這個列是普通索引字段,innodb一般會走普通索引,每返回一行數據,server層就會判斷這個字段是否為null,不是null的情況下+1。當然如果建表sql里字段定義為not null的話,那就不用做這一步判斷直接+1。

如果這個列沒有加過索引,那innodb可能會全表掃描,返回的每一行數據,server層都會判斷這個字段是否為null,不是null的情況下+1。同上面的情況一樣,字段加了not null也就省下這一步判斷了。

理解了原理后我們大概可以知道他們的性能排序是

1

count(*) ≈ count(1) > count(主鍵id) > count(普通索引列) > count(未加索引列)

所以說count(*),已經是最快的了。

知道真相的我眼淚掉下來。?

那有沒有其他更好的辦法?

允許粗略估計行數的場景

我們回過頭來細品下文章開頭的需求,我們只是希望知道數據庫里還有多少短信是堆積在那沒發的,具體是1k還是2k其實都是差不多量級,等到了百萬以上,具體數值已經不重要了,我們知道它現在堆積得很離譜,就夠了。因此這個場景,其實是允許使用比較粗略的估計的。

那怎么樣才能獲得粗略的數值呢?

還記得我們平時為了查看sql執行計劃用的explain命令不。

其中有個rows,會用來估計接下來執行這條sql需要掃描和檢查多少行。它是通過采樣的方式計算出來的,雖然會有一定的偏差,但它能反映一定的數量級。

explain里的rows

有些語言的orm里可能沒有專門的explain語法,但是肯定有執行raw sql的功能,你可以把explain語句當做raw sql傳入,從返回的結果里將rows那一列讀出來使用。

一般情況下,explain的sql如果能走索引,那會比不走索引的情況更準 。單個字段的索引會比多個字段組成的復合索引要準。索引區分度越高,rows的值也會越準。

這種情況幾乎滿足大部分的監控場景。但總有一些場景,它要求必須得到精確的行數,這種情況該怎么辦呢?

必須精確估計行數的場景

這種場景就比較頭疼了,但也不是不能做。

我們可以單獨拉一張新的數據庫表,只為保存各種場景下的count。

1

2

3

4

5

6

7

CREATE TABLE `count_table` (

??`id` int NOT NULL AUTO_INCREMENT COMMENT '主鍵',

??`cnt_what` char(20) NOT NULL DEFAULT '' COMMENT '各種需要計算的指標',

??`cnt` tinyint NOT NULL COMMENT 'cnt指標值',

??PRIMARY KEY (`id`),

??KEY `idx_cnt_what` (`cnt_what`)

) ENGINE=InnoDB? DEFAULT CHARSET=utf8mb4;

count_table表保存各種場景下的count

當需要獲取某個場景下的cout值時,可以使用下面的sql進行直接讀取,快得飛起。

1

select cnt from count_table where cnt_what = "未發送的短信數量";

那這些count的結果值從哪來呢?這里分成兩種情況。

實時性要求較高的場景

如果你對這個cnt計算結果的實時性要求很高,那你需要將更新cnt的sql加入到對應變更行數的事務中。比如我們有兩個事務A和B,分別是增加未發送短信和減少未發送短信。

將更改表行數的操作放入到事務里

這樣做的好處是事務內的cnt行數依然符合隔離級別,事務回滾的時候,cnt的值也會跟著回滾。

壞處也比較明顯,多個線程對同一個cnt進行寫操作,會觸發悲觀鎖,多個線程之間需要互相等待。對于高頻寫的場景,性能會有折損。

實時性沒那么高的場景

如果實時性要求不高的話,比如可以一天一次,那你可以通過全表掃描后做計算。

舉個例子,比如上面的短信表,可以按id排序,每次取出1w條數據,記下這一批里最大的id,然后下次從最大id開始再拿1w條數據出來,不斷循環。

對于未發送的短信,就只需要在撈出的那1w條數據里,篩選出state=0的條數。

batch分批獲取短信表

當然如果有條件,這種場景最好的方式還是消費binlog將數據導入到hive里,然后在hive里做查詢,不少公司也已經有現成的組件可以做這種事情,不用自己寫腳本,豈不美哉。

mysql同步hive

總結

mysql用count方法查全表數據,在不同的存儲引擎里實現不同,myisam有專門字段記錄全表的行數,直接讀這個字段就好了。而innodb則需要一行行去算。

性能方面?count(*) ≈ count(1) > count(主鍵id) > count(普通索引列) > count(未加索引列),但哪怕是性能最好的count(*),由于實現上就需要一行行去算,所以數據量大的時候就是不給力。

如果確實需要獲取行數,且可以接受不那么精確的行數(只需要判斷大概的量級)的話,那可以用explain里的rows,這可以滿足大部分的監控場景,實現簡單。

如果要求行數準確,可以建個新表,里面專門放表行數的信息。

如果對實時性要求比較高的話,可以將更新行數的sql放入到對應事務里,這樣既能滿足事務隔離性,還能快速讀取到行數信息。

如果對實時性要求不高,接受一小時或者一天的更新頻率,那既可以自己寫腳本遍歷全表后更新行數信息。也可以將通過監聽binlog將數據導入hive,需要數據時直接通過hive計算得出。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/41941.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/41941.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/41941.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

004 返回值處理

文章目錄 不使用注解修飾ModelAndViewvoidString(推薦) 使用注解修飾ResponseBody注解常用的HttpMessageConverter 不使用注解修飾 ModelAndView Controller方法中定義ModelAndView對象并返回,對象中可添加model數據、指定view。 void 在Co…

關于linux服務器更改鏡像后連接不上vscode問題

問題樣子解決辦法直接看 問題樣子 問題描述:從centos換到ubantu后,xshell能直接連接上(沒有更改ssh配置信息),但是vscode連不上(配置文件因為端口號和ip是一樣的,也沒法改) 猜測…

基于支持向量機、孤立森林和LSTM自編碼器的機械狀態異常檢測(MATLAB R2021B)

異常檢測通常是根據已有的觀測數據建立正常行為模型,從而將不同機制下產生的遠離正常行為的數據劃分為異常類,進而實現對異常狀態的檢測。常用的異常檢測方法主要有:統計方法、信息度量方法、譜映射方法、聚類方法、近鄰方法和分類方法等。 …

Android仿今日頭條新聞(一)

新建一個側邊欄的文件,創建成功后直接運行。可以看到帶滑動的側邊欄功能如圖所示: 主體UI: 新聞UI的實現: 側邊欄: 更換一下顏色: 學習參考-浩宇開發

AI老照片生成視頻

地址:AI老照片 讓你的圖片動起來, 老照片修復與動態化

RK3568平臺(opencv篇)opencv處理圖像視頻

一.讀取圖像文件并展示 灰度圖像: 灰度圖需要用 8 位二進制來表示,取值范圍是 0-255。用 0 表示 0(黑色), 用 255 表示 1(白色),取值越大表示該點越亮。 RGB 彩色圖像:…

XPath 語法筆記

XPath 語法筆記 XPath 表達式運算符謂語(Predicates)1. 選擇所有本地名稱為特定名稱的元素2. 選擇具有特定屬性的元素3. 選擇屬性值不等于特定值的元素4. 選擇元素內容中包含特定字符串的節點5. 選擇元素內容中不包含特定字符串的節點6. 選擇特定命名空間…

一臺docker機器如何實現構建多平臺鏡像

1.實現方式 想在 x86_64/amd64 的一臺機器平臺上,構建適用于多個平臺的鏡像,例如 linux/amd64、linux/arm64,根據官方文檔,有三種方法可以使用: QEMU ??使用 QEMU 模擬構建多平臺映像是最簡單的入門方法&#xff0c…

前端面試題13(API請求方法)

在前端JavaScript中,進行API請求主要可以通過幾種方式來實現,最常見的是使用XMLHttpRequest(較舊的方法)、fetch(現代瀏覽器推薦方法)以及使用第三方庫如axios或jQuery.ajax等。 1. XMLHttpRequest 這是最…

仙人掌中的SNMP檢測不到服務器

登錄有問題的服務器1.檢測snmp localhost:~ # ps -ef|grep snmp root 55180 1 0 08:37 ? 00:00:08 /usr/sbin/snmpd -r -A -LF n /var/log/net-snmpd.log -p /var/run/snmpd.pid root 58436 53989 0 09:44 pts/0 00:00:00 grep --colorauto snmp2.檢測…

linux 編譯perf 工具

centos7下編譯步驟: wget https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/linux-3.10.tar.gz tar -xf linux-3.10.tar.gz cd linux-3.10/tools/ yum install -y flex bison python2-devel make perf ./perf/perf top -d 1…

昇思第10天

RNN實現情感分類 二分類問題:Positive和Negative兩類 步驟: 1.加載IMDB數據集 2.加載預訓練詞向量:預訓練詞向量是對輸入單詞的數值化表示,通過nn.Embedding層,采用查表的方式,輸入單詞對應詞表中的index,…

【算法專題】模擬算法題

模擬算法題往往不涉及復雜的數據結構或算法,而是側重于對特定情景的代碼實現,關鍵在于理解題目所描述的情境,并能夠將其轉化為代碼邏輯。所以我們在處理這種類型的題目時,最好要現在演草紙上把情況理清楚,再動手編寫代…

FreeRTOS——隊列集

一、隊列集 一個隊列只允許任務間傳遞的消息為 同一種數據類型 ,如果需要在任務間 傳遞不同數據類型的消息 時,那么就可以使用隊列集 作用:用于對多個隊列或信號量進行“監聽”(接收或獲取),其中 不管哪一…

js 使用 lodash-es 檢測某個值是否是函數

import { isFunction } from lodash-eslet isA isFunction(() > {}) console.log(isA) //true https://www.lodashjs.com/docs/lodash.isFunction#_isfunctionvalue https://lodash.com/docs/4.17.15#isFunction 人工智能學習網站 https://chat.xutongbao.top

Spring框架配置進階_自動裝配(XML和注解)

Spring配置進階 Spring 容器提供配置元數據有三種方式 XML配置文件。基于注解的配置。基于java的配置。 一、自動裝配 應用程序上下文為你找出依賴項的過程,Spring會在上下文中自動查找,并自動給bean裝配與其關聯的屬性 Spring中實現自動裝配的方式有兩種: XML文…

26-ARM常用匯編指令

匯編格式: label:instruction comment label:標號instruction:具體匯編指令comment:注釋內容 常用段名: .text:代碼段.data:初始化的數據段.bss:未初始化的數據段.ro…

Spring Boot+Vue項目從零入手

Spring BootVue項目從零入手 一、前期準備 在搭建spring bootvue項目前,我們首先要準備好開發環境,所需相關環境和軟件如下: 1、node.js 檢測安裝成功的方法:node -v 2、vue 檢測安裝成功的方法:vue -V 3、Visu…

JSP WEB開發(一) JSP語言基礎

目錄 JSP JSP簡介: JSP頁面 JSP運行原理 JSP腳本元素 JAVA程序片 局部變量 全局變量和方法的聲明 全局變量 方法的聲明 程序片執行特點 synchronized關鍵字 表達式 JSP指令標記 page指令 include指令 JSP動作標記 JSP動作元素include和include指令的…

Docker在人工智能領域的應用與實戰

摘要 人工智能(AI)技術的快速發展帶來了對高效開發和部署工具的需求。Docker作為一個創新的容器化平臺,為AI領域提供了強大的支持。本文詳細介紹了Docker在AI模型開發、訓練、部署以及服務器集群管理等方面的應用,并探討了其在數…