MySQL優化高手筆記

語雀完整版:

https://www.yuque.com/g/mingrun/embiys/dv3btw/collaborator/join?token=zMBwPzSMfSGINLuv&source=doc_collaborator# 《MySQL優化高手筆記》

MySQL優化高手

一、MySQL架構

01 天天寫CRUD,你知道你的系統是如何跟MySQL打交道的嗎

通過驅動連接數據庫,而且會創建多個連接,可通過連接池進行管理,避免頻繁重復創建連接,浪費資源

02 為了執行SQL語句,你知道MySQL用了什么樣的架構設計嗎

除連接數據庫維護一個連接池外,數據庫本身也會維護一個連接池,其中有一個不變的原則,既然是網絡連接,那就一定會有一個線程去監聽處理,Mysql維護了一個sql接口用以接收各種語句,然后Sql接口將語句交給Sql解析器進行解析,然后找查詢優化器選擇一個最優的查詢路徑(好比到達終點有很多條路,選擇最優的那個方案,特別是復雜大Sql)。

優化器選擇好執行路徑后,就交給存儲引擎(去哪執行,去哪查,怎么執行)去真正的執行,

除此之外還有執行器,是他拿著優化器生成的執行方案去調用存儲引擎。

03 用一次數據更新流程,初步了解InnoDB存儲引擎的架構設計

04 借著更新語句在InnoDB存儲引擎中的執行流程,聊聊binlog是什么

binlog與redo log不同,它不屬于innodb這一個引擎,而是屬于整個MySql Server,其中前四步是執行sql,5、6步是提交事務,redo log和bin log寫入磁盤。
sync_binlog:是控制binglog刷磁盤的策略,默認是0,先進入Oscache,設置為1測試直接刷入磁盤。
當binlog 刷入磁盤后就算是事務提交完畢了,然后還需要在redo log中寫入一個commit標記,和binlog的文件名、路徑,寫入一個commit標記是為了確保binlog已經刷入了磁盤。

接著就是一個Io線程隨機的把臟數據緩存池中的數據刷回磁盤,如果此時宕機也不會造成問題,I/O線程依然可以根據redo把數據找回數據。

05 生產經驗:真實生產環境下的數據機器配置如何規劃

  1. Java應用系統: 2c4g -> 4c8g ?>> 一兩百、每秒500左右
  1. 數據庫: 8c16g -> 16c32g ?>> 一兩千、三四千
  1. 對于Java系統I/O操作比較少,耗時的一般是數據庫,他會對磁盤文件進行大量的I/O操作,同時還有網絡傳輸上的耗時。

06 生產經驗:互聯網公司的生產環境數據庫是如何進行性能測試的

  1. 最好對數據庫先做壓測,因為java系統可能和數據所能承載的訪問量不一樣,嘗試每秒發送一千、兩千個請求。
  1. QPS(Query Per Second)TPS(Transaction Per Second):前者是每秒處理多少個請求,或者說數據庫執行多少個SQL語句。 TPS則是每秒可以處理的提交或回滾事務。
  1. 然后再從I/O指標,CPU負載,網絡負載,內存負載,進行壓測。

07 生產經驗:如何對生產環境中的數據庫進行360度無死角玉測

使用 sysbench ?對數據庫進行360全方位壓測。

08 生產經驗:在數據庫的壓測過程中.如何360度無死角觀察機器性能

  1. 逐漸加大請求量,觀察機器的狀態
  1. top命令觀察cpu負載,內存情況,dstat -d命令查看I/O吞吐量,dstat -n ?查看網絡流量情況。

09 生產經驗:如何為生產環境中的數據庫部署監控系統

Prometheus(數據采集)+Grafana(報表):實現一套可視化監控系統

10 生產經驗:如何為數據庫的監控系統部署可視化報表系統

Prometheus(數據采集)+Grafana(報表):實現一套可視化監控系統

二、Buffer Pool

11從數據的增刪改開始講起,回顧一下Buffer Pool在數據庫里的地位

  1. buffer pool是一個非常重要的組件,一般大磁盤的隨機讀寫可能要幾百毫秒,如果直接查找磁盤效率很低
  1. 增刪改的操作首先操作的就是buffer pool,同時結合了 redo log,刷盤機制,

12 Buffer Pool這個內存數據結構到底長個什么樣子

  1. 128兆默認的有點小 --> 15c32g(推薦2g)
  1. 抽象的數據結構 -> 數據頁:每一頁中放了許多行
  1. 磁盤中的數據每一頁16kb,與此對應的緩存頁也是16kb
  1. 描述數據在緩存頁前面,包含表空間、數據頁的編號、此頁在buffer pool中的地址等信息,在buffer pool中描述數據大概相當于緩存頁的5%。

13從磁盤讀取數據頁到Buffer Pool的時候,free鏈表有什么用

  1. 初始化buffer pool:初始化內存空間,每頁16kb何其對應的800字節的描述數據,然后數據庫運行時,執行增刪改時再將數據加載進buffer pool里面
  1. 哪些數據頁是空閑的?
    buffer pool擁有一個Free 鏈表(雙向),他存放了空閑的緩存頁的描述數據塊的地址
  1. 將磁盤中的數據加載進buffer pool:從鏈表中獲取一個空閑的緩存頁,然后就將數據頁和對應的描述信息寫入,最后再將這個節點從緩存頁中刪除。

  1. 判斷數據頁是否已被緩存:

14當我們更新Buffer Pool中的數據時,flush鏈表有什么用

臟頁:指那些在緩存池中修改,尚未刷回磁盤的數據,而有的緩存頁是查詢產生的,不需要刷回磁盤,于是就有了flush鏈表,他存放的是被修改過的緩存頁的描述數據的地址(指針)。如果某個緩存頁被修改,那么他就會加入這個filush 鏈表。

15當Buffer Pool中的緩存頁不夠的時候,如何基于LRU算法淘汰部分緩存

  1. 把修改過數據的緩存頁刷新到磁盤中去,騰出來新的空閑緩存頁
  1. 緩存命中率: 只從緩存中操作的次數/總請求次數, 可根據命中率決定淘汰哪個。
  1. LRU鏈表:最近使用過的放在鏈表頭部,然后就淘汰鏈表尾部的。

16簡單的LRU鏈表在Buffer Pool實際運行中,可能遇到哪些問題

  1. 預讀機制
    1. 預讀機制帶來的問題:從磁盤加載數據頁的時候,也會把它的左右鄰居帶上,并且會在LRU鏈表的前面,但實際上它的鄰居并沒有被訪問,而合理的情況下是要把這些沒人訪問的鄰居給清理掉。
    2. 觸發預讀機制的情況:
      innodb_read_ahead_threshold ,最多連續訪問了相鄰的56(默認)數據頁,就會觸發。
      innodb_random_read_ahead ?,默認Off,存放了13個相鄰的,頻繁訪問,就觸發。
  1. 全表掃描(select * 并且沒有where條件)
    會將這個表所有的數據頁加載進緩存頁里,占用lru鏈表的頭部。

17 MySQL是如何基于冷熱數據分離的方案,來優化LRU算法的

  1. 設計預讀就是感覺你連續訪問了很多相鄰頁,那么很有可能接下來還是,理想很豐滿,就是怕實際上沒人訪問。
  1. 而mysql中因為上述的問題會將 lru鏈表分為冷熱兩個部分,冷數據由nnodb_old_blocks_pc,控制,默認占37%。
  1. 數據頁第一次被放到緩存的時候,就會被放到冷數據區的頭部, 此后innodb_old_blocks_time (默認1s) 后,如果還有訪問就放到熱區頭部,就是為了防止你只用了一次放到熱區

18 基于冷熱數據分離方案優化后的LRU鏈表,是如何解決之前的問題的

完美

19 MySQL是如何將LRU鏈表的使用性能優化到極致的

  1. 熱數據區節點移動優化:
    為了防止移動過于頻繁,實際上只有后3/4 的數據被訪問了才會被移動到鏈表頭部

20 對于LRU鏈表中尾部的緩存頁,是如何淘汰他們刷入磁盤的

  1. 使用回顧:從磁盤中加載一個緩存頁,就會從free鏈表移除這個緩存頁,然后在lru冷數據 頭部加入這個緩存頁。
    如果修改了一個緩存頁,那么flush列表就會記錄這個臟頁,而且還可能會把這個臟頁從lru冷數據頭部移動到熱數據頭部。
  1. 在執行CRUD操作時,如果緩存頁已滿則會把一些緩存頁刷入磁盤,而刷入磁盤頁也會有幾個時機
  1. 將LRU尾部緩存頁刷入磁盤的幾個時機:
    • 不等緩存頁用完,有個定時任務,將lru尾部的緩存頁刷入磁盤從flush鏈表中刪除,加入free鏈表。
    • 定時將flush鏈表中的緩存頁刷入磁盤,從而將其從lru鏈表中移除。

21 生產經驗: 如何通過多個Buffer Pool來優化數據庫的并發性能

  1. buffler pool本質上就是一大塊內存區域,里面存放了數據頁和描述數據塊,同時有free、flush、lru來輔助它的運行。
  1. 多個線程操作buffer pool是要排隊的,性能肯定會下降,所以可以調大內存設置多個buffer pool, 有多少個buffer pool ,每個buffer pool的大小。

22 生產經驗: 如何通過chunk來支持數據庫運行期間的Buffer Pool動態調整

  1. 不可以直接動態調整buffer pool的大小: 比如從8G調整到 16G,會復制大量的緩存頁,描述數據等,不可接受。
  1. chunk:buffer pool中包含了多個chunk

  1. 此時如果要增加內存 那就增加chunk的數量就行了。

23 生產經驗:在生產環境中,如何基于機器配置來合理設置Buffer Pool

1. 要考慮系統內核運行所占用的內存,和其他人需要的內存,一般設置為總內存的 50% ~60%

三、存儲結構

24 ?我們寫入數據庫的一行數據,在磁盤上是怎么存儲的

  1. 邏輯概念:表&行&字段 -> ?物理概念:表空間&數據區&數據頁
  1. 為什么不能直接操作磁盤?對磁盤的隨機讀寫很耗時,而內存中修改數據則會省時不少,高配置的機器上才可以看下每秒幾千的請求。
  1. 每次都加載一條數據到磁盤中再修改,效率不高,所以有了數據頁,每頁16kb,包含多條數據。 要改一條數據就把這條數據的數據頁假如緩存。
    而更新的時候,也是以頁為單位,刷新多條數據。
  1. 每一行數據在磁盤上如何存儲?
    創建表時會指定一個行格式,比如COMPACT格式,實際每一行存儲時就像下面這樣
變長字段的長度列表,null值列表,數據頭,column01的值,column02的值,column0n的值......

25 對于VARCHAR這種變長字段,在磁盤上到底是如何存儲的

  1. 一行數據在磁盤上存儲的時候還包含其它描述數據。
  1. 表里面很多行數據,最終放到磁盤就是一大坨數據挨著放到一塊的。而變長數據如varchar,這時就變的難以讀取,因為無法確定一行的長度,所以就引入了變長字段的長度列表,他記錄這個變長數據的長度。
0x05 null值列表 數據頭 hello a a 0x02 null值列表 數據頭 hi a a

26 一行數據中的多個NULL字段值在磁盤上怎么存儲

  1. 實際上一個null 值,比如name字段的值為null,是不可能直接設置為null字符串的
  1. null值是以 二進制的bit位來存儲的,下面的案例中后四個都是為null的,且都是變長字段,
name  addreass  gender  job     school
jack    NULL      m     NULL   xx_school// 最終的行格式 其中0101是NULL值列表,它對應著上面四個可為null的字段,0不是null 1是null
0x09 0x04 0101 頭信息 column1=value1 column2=value2 ... columnN=valueN// 實際上null值列表的長度是 8的倍數,不足高位補零
0x09 0x04 00000101 頭信息 column1=value1 column2=value2 ... columnN=valueN

27 磁盤文件中,40個bit位的數據頭以及真實數據如何存儲

  1. 除上面所述外,每一行數據還有 40個bit的數據頭,用來描述這行數據
  1. 1~2位 :預留位,無意義
  1. 3位:delete_mask該行是否已被刪除
  1. 4位:min_rec_mask每一層的非葉子節點都有最小值
  1. 5~8:n_owned ?記錄數
  1. 9~21:heap_no 當前這行數據在堆里面的位置
  1. 22~24:record_type 這行數據的類型,0代表的是普通類型,1代表的是B+樹非葉子節點,2代表的是最小值數據,3代表的是最大值數據
  1. 25~40:next_record,下一條數據的指針

28 我們每一行的實際數據在磁盤上是如何存儲的

  1. 每一行數據在磁盤中的存儲:
name  addreass  gender  job     school
jack    NULL      m     NULL   xx_school
// 變長字段的長度列表(字段倒敘) + NULL值列表(字段倒敘)+數據頭+真實數據
0x09 0x04 00000101 0000000000000000000010000000000000011001 jack m xx_school 
//而實際上這些字符串在磁盤上存儲的是指定編碼集編碼之后的
0x09 0x04 00000101 0000000000000000000010000000000000011001 616161 636320 6262626262

  1. 實際上在真實數據部分 還有隱藏字段:
    DB_ROW_ID ?每一行的一個標識
    DB_TRX_ID ?事務Id
    DB_ROLL_PTR ?回滾指針
// 至此真實數據應如下所示,而緩存池中加載的數據(每個數據頁/緩存頁裝載的數據頁) 實際上也如下所示
0x09 0x04 00000101 0000000000000000000010000000000000011001 00000000094C(DB_ROW_ID)
00000000032D(DB_TRX_ID) EA000010078E(DB_ROL_PTR) 616161 636320 6262626262

29 理解數據在磁盤上的物理存儲之后,聊聊行溢出是什么東西

  1. 每個數據頁/緩存頁只有16kb大小,如果存儲的真實數據過大,就會出現行溢出的這種情況,然后就會出現一行數據在多個數據頁/緩存 頁存儲的情況。

30 于存放磁盤上的多行數據的數據頁到底長個什么樣子

  1. 如果此時數據庫一條數據都沒有,要插入一條數據就要先從磁盤中加載一個空的數據頁,然后向這個數據頁插入數據行。 而實際上要明白磁盤中的數據行是和buffer pool中的緩存行是一一對應的,而后臺的IO線程也會定時的根據lru鏈表和flush鏈表 將臟數據刷入磁盤

31 表空間以及劃分多個數據頁的數據區

  1. 表空間:有的表空間如系統表空間對應多個磁盤上的.idb文件,有的只對應一個,然后每個表空間會對應多個數據頁。
  1. 數據區(extent):表空間包含多個數據頁,不便于管理,于是有了數據區,一個數據區對應連續64個數據頁,一個數據頁64kb大小,一個數據區就是1m大小,然后256個數據區劃分為一組。
  1. 第一個組的第一個數據區的前三個數據頁是固定的,用來存放描述性數據:
    FSP_HDR:存放了表空間和這一組數據區的一些屬性。
    IBUF_BITMA:這一組數據頁的所有insert buffer的一些信息。
    INODE:也存放了一些特殊的信息
  1. 每個表空間,除第一組數據區外,其它組的第一個數據區 的前兩個數據頁也是用來存放一些特殊信息的。
  1. 關系梳理: 表->對應的表空間->對應磁盤上的idb文件->多組數據區->數據頁

32 一文總結初步了解到的MySQL存儲模型以及數據讀寫機制

  1. 表是邏輯概念,對應物理層面就是表空間
  1. 插入一條數據:先找到表空間,Extent組,extent,然后找到數據頁,最后就可以加載到緩存池中了。

33 MySQl數據庫的日志順序讀寫以及數據文件隨機讀寫的原理

  1. 磁盤隨機讀:從磁盤中隨機讀一個數據頁放到緩存中,這個數據頁可能在磁盤中的任意一個位置,所以性能較差。
  1. IOPS和響應延遲 :這是隨機讀最需要關心的指標,代表磁盤每秒可以有多少次隨機讀操作
  1. 磁盤順序寫:緩存頁中數據更新后會寫一條redo log, 它是順序寫的,在末尾進行日志追加,而如果走OSchache的話效率會更高

34 生產經驗: Linux操作系統的存儲系統軟件層原理剖析以及IO調度優化原理

調度算法: ?CFQ公平算法就是挨個排隊,而deadline 算法,可能讓IO等待時間更少的操作先執行

35 生產經驗: 數據庫服務器使用的RAID存儲架構初步介紹

  1. RAID架構:
    很多數據庫部署時都采用他,它是一個磁盤冗余陣列,在實際生產中,如果服務器磁盤不夠用就會加磁盤,而RAID就是管理機器中的多塊磁盤的一種磁盤陣列技術,它可以告訴你該向那塊磁盤進行讀寫。
  1. 數據冗余機制:
    將數據在另一塊磁盤中做冗余備份,如果一塊磁盤掛掉,則還可以使用另一塊磁盤,RAID技術則可以自動進行管理。而整體上RAID既有硬件層面也有軟件層面。

36 生產經驗:數據庫服務器上的RAID存儲架構的電池充放電原理

  1. 多塊磁盤組成RAID陣列時,會有一塊RAID卡,這塊卡是帶一個塊緩存的,緩存模式設置成write back后,數據就會先進入到緩存里,然后再慢慢寫入磁盤,這樣能大幅提高性能
  1. 但如果SDRAM突然斷電,那么數據就會丟失,所有有鋰電池來保證供電,直到數據刷回磁盤,但是鋰電池有性能衰減問題,要定時充放電,而在這個過程中,緩存模式就會設置成write through,直接寫硬盤,性能從0.1毫秒級 退化成毫秒級。然后數據庫的性能也會因此出現幾十倍的抖動。

37 案例實戰: RAID鋰電池充放電導致的MySQL數據庫性能抖動的優化

  1. 一般廠商設置30天充放電一次,每次數據庫出現性能抖動,性能下降10倍以上。
  1. 可以使用RAID 設備提供的命令查看日志。
  1. 解決這個問題有三種方案:
    換成電容,但不常用,更換麻煩,易老化。
    手動充放電,使用腳本在夜深人靜時觸發
    充放電時不關閉write back,它可以和上面的這個策略配合使用。

38 案例實戰:數據庫無法連接故障的定位,Too many connections

  1. 兩個Java系統設置最大連接數200個,那就有400個,而mysql也要建立400個網絡連接,對于高配置Mysql服務器是完全可以辦到的
  1. 先檢查 my.conf 中的max_connections ,再使用命令查看當前的連接數量show variables like 'max_connections' ?再檢查mysql的啟動日志,如下所示,mysql無法為我們設置我們的期望值
Could not increase number of max_open_files to more than mysqld (request: 65535)
Changed limits: max_connections: 214 (requested 2000)
Changed limits: table_open_cache: 400 (requested 4096)

  1. 上面的原因就是因為句柄的限制

39 案例實戰:如何解決經典的Too many connections故障? 背后原理是什么

  1. 解決:
ulimit -HSn 65535-- 然后就可以用如下命令檢查最大文件句柄數是否被修改了
cat /etc/security/limits.conf
cat /etc/rc.local

  1. 然后就重啟服務器就ok了
  1. linux系統作此限制的目的就是為了防止某一線程占用過多的資源,一般如kafka之類的中間件都需要自行設置參數。

四、Redo Log

40 重新回顧redo日志對于事務提交后,數據絕對不會丟失的意義

  1. redo log記錄著對數據庫的修改,如果提交事務后能保證刷回磁盤,出現故障后就能基于它進行重做
  1. 如果沒有rodo log 那么最大的缺陷就是,在IO線程還沒有把數據刷回磁盤就時突然宕機,而且也不可能提交一次事務就將數據刷新入磁盤,因為隨機讀寫的效率很低。
  1. redo log的格式大致為:對表空間XX中的數據頁XX中的偏移量為XXXX的地方更新了數據XXX
  1. 直接將數據刷入磁盤和將日志寫入Redo log的區別:
    二者都是寫入磁盤,但直接刷入磁盤要以數據頁為單位,一個數據頁16kb,僅僅修改了幾個字節就要刷入整個數據頁。
    而一行redo log就可能只占用幾十個字節,就包含表空間號、數據頁號、磁盤文件偏移量、更新值,寫入磁盤速度很快

41 在Buffer Pool執行完增刪改之后,寫入日志文件的redo log長什么樣

  1. 根據修改的字節的個數不同 redo log劃分了幾種類型,MLOG_1BYTE (修改了一個字節),MLOG_2BYTE
//MOLOG_1BYTE
表空間ID,數據頁號,數據頁中的偏移量,具體修改的數據
//MLOG_WRITE_STRING 不知道具體修改了幾個字節的數據
表空間ID,數據頁號,數據頁中的偏移量,修改數據長度,具體修改的數據

42 redo log是直接一條條寫入文件的嗎? 非也,揭秘redo log block

  1. redo log 不是把所有行的日志直接寫到一堆,而是用redo log block ?來存放多個單行日志,一個block是512字節

  1. 寫入流程:先在內存中湊夠一個數據塊,然后在寫入磁盤文件中

43 直接強行把redo log寫入磁盤?非也,揭秘redo log buffer

  1. redo log buffer:申請出來的一塊連續的內存空間,劃分出了連續的多個redo log block,然后上面說的redo log都是先寫入這里的
  1. 寫時從第一個開始寫入,而且實際上再一次事務中可能涉及到多個redo log,他們湊成一組寫入到 redo log buffer中的 redo log block,最后再刷入磁盤中的redo log block。

44 redo log buffer中的緩沖日志,到底什么時候可以寫入磁盤?

  1. redo log buffer 寫入磁盤的時機:
    • 寫入redo log buffer中的日志已經占到了總容量的一半
    • 提交事務時要提交redo log,就要把redo log對應的redo log block刷入到磁盤中
    • 后臺線程每隔一秒將redo log buffer刷入磁盤
    • mysql關閉的時候
  1. 磁盤中默認有兩個日志文件,每個48M,能容納百萬條redo log, 如果第二個寫滿了就寫到第一個如此往復。

五、事務與鎖機制

45 如果事務執行到一半要回滾怎么辦?再探undo log回滾日志原理!

  1. undo log用來做回滾,如果一次任務失敗就要把buffer pool中的操作給回滾掉
  1. 比如是insert語句,那么undo中存的就是主鍵和 delete語句,能把你的操作回退掉,如果是update語句,就記錄原來的值,把舊值給更新回去。

46 一起來看看INSRET語句的undo log回滾日志長什么樣?

  1. insert語句的undo log的類型是TRX_UNDO_INSERT_REC,它包含了:
這條日志的開始位置//主鍵中的每列的長度、值,如果沒有主鍵就設置row_id為主鍵
主鍵的各列長度和值 
表id
undo log日志編號//TRX_UNDO_INSERT_REC
undo log日志類型
這條日志的結束位置

  1. 有了該日志之后就知道了哪個表插入的數據,主鍵,注解定位到緩存頁,然后刪除掉之前插入的數據。

47 簡單回顧一下,MySQL 運行時多個事務同時執行是什么場景?

多線程操作buffer pool

48 多個事務并發更新以及查詢數據,為什么會有臟寫和臟讀的問題?

  1. 臟寫:A寫,B寫,A回滾,B讀,發現數據不是自己寫的。(B讀了A修改后但還未提交的數據,A回滾會覆蓋B的值)
  1. 臟讀:事務B用了事務A修改后的值,然后事務A回滾,導致事務B拿到的就是臟值。
  1. 他們倆的問題就是 在別人還沒提交事務的時候就進行讀寫操作。

49 一個事務多次查詢一條數據讀到的都是不同的值,這就是不可重 復讀?

  1. 不可重復讀:A事務讀取一個值->B事務修改一個值并提交->C事務修改一個值并提交,A事務再讀就發現和自己原來設置的值不同了。
  1. 可重復讀:和上面相反。

50 聽起來很恐怖的數據庫幻讀,到底是個什么奇葩問題 ?

  1. 幻讀:執行同一條sql語句比如 select *from table where id>10 , 多次執行中間有其它事務提交了數據導致數據增多,多次查詢的結果不一致

51 SQL標準中對事務的4個隔離級別,都是如何規定的呢?

  1. 根據并發可能出現的問題出現了幾個隔離級別
  1. 下圖中少了個臟寫,READ-UNCONMMITTEDA是可以防止臟寫的。
  1. 其中常用的是RC和RR

52 MySQL是如何支持4種事務隔離級別的? Spring事務注解是如何設置的?

  1. MySQL的隔離級別和SQL標準中的略有不同,它默認的隔離級別是R-R,同時它的RR是可以防止幻讀的。
  1. @Transactional(isolation=Isolation.DEFAULT) ?該注解就用mysql默認的讀寫機制
  1. 如果要自行改動mysql的隔離級別的話,就可能會用READ-COMMITTED,讓每次讀的數據都不一樣。

53 理解MVCC機制的前奏: undo log版本鏈是個什么東西?

  1. Mysql之所以能做到多個事務平行執行,彼此之間的數據互不打擾是因為使用了MVCC(Multi-Version Concurrency Control) 多版本并發控制
  1. Undo log版本鏈:每行數據有兩個隱藏的字段 txr_id(最近一次更新這條數據的 事務的Id)和roll_pointer (指向一個undo log)
  1. 事務A插入一條數據,記錄值A和新的事務Id,roll_pointer指向一個空的undo log
  1. 事務B 將值更新為值B,同時要生成一個undo log,記錄之前的值、事務id、roll_pointer
    生成后就把這行數據的roll_pointer 指向這個生成的undo log,至此就會出現一條undo log鏈條

54 基于undo log多版本鏈條實現的ReadView機制,到底是什么?

  1. ReadView:每次執行事務的時候生成一個,最關鍵的東西有四個
一個是m_ids,這個就是說此時有哪些事務在MySQL里執行還沒提交的;
一個是min_trx_id,就是m_ids里最小的值;
一個是max_trx_id,這是說mysql下一個要生成的事務id,就是最大事務id;
一個是creator_trx_id,就是你這個事務的id

  1. ReadView讀視圖機制敘述:
    首先他是基于undo log 鏈實現的一套讀視圖,事務執行時會生成一個readView,在這次事務id之前的就是已經提交過的事務,可以直接讀取。 在這次事務id之后的表明又有其它事務修改了數據,不能直接讀到,需要沿著undo log鏈條找到自己的事務或者小于自己事務id的數據。

55 Read Comitted隔離級別是如何基于ReadView機制實現的?

56 MySQL最牛的RR隔離級別,是如何基于ReadView機制實現的?

  1. RR隔離級別與上面樣式ReadView用畫了同一個流程圖
  1. RR隔離級別與RC不同的就是 它的ReadView 是這個事務第一次查詢的時候只生成一次

57 停一停腳步:梳理一下數據庫的多事務并發運行的隔離機制

  1. 事務隔離級別
  1. ReadView+undo log 鏈

58 多個事務更新同一行數據時,是如何加鎖避免臟寫的?

  1. 并發更新一條數據就會有臟寫問題,解決臟寫問題則需要鎖機制,然后在多個事務更新一條數據的時候就是串行化的
  1. 事務A看到當前行未加鎖就會創建一個鎖,這個鎖包含事務Id和等待狀態。
  1. 事務B過來時也要創建鎖,只不過因為事務A它的等待狀態 要設置為true。
  1. 事務A執行完后 要更改事務B的等待狀態,去喚醒他。
  1. 總結:其實就是加了獨占鎖 才避免了臟寫問題

59 對MySQL鎖機制再深入一步,共享鎖和獨占鎖到底是什么?

  1. 上面多個數據更新同一行數據時加的是獨占鎖(Exclude),也就是X鎖,一次只能有一個在寫數據,其他人排隊,但是其它事務要讀取數據的話是不用加鎖的,因為有Mvcc機制的存在。
  1. 如果在讀取數據的時候也非要加鎖,可以使用共享鎖,在查詢語句后面加上lock in share mode
  1. 共享鎖與獨占鎖互斥,加了共享鎖就不能加互斥鎖,反過來也一樣

  1. 查詢操作也可以加獨占鎖, select * from table for update,代表我查詢的時候別人都不能更新

60 在數據庫里,哪些操作會導致在表級別加鎖呢?

  1. 一般情況下不建議 手動在SQL語句上加鎖,那樣加鎖邏輯就會隱藏在sql語句中
  1. 表級鎖:一般指存儲引擎的鎖,而不是DDL語句的鎖,雖然執行DDL語句的時候 其它對數據的操作會阻塞。

61 表鎖和行鎖互相之間的關系以及互斥規則是什么呢?

  1. 加表鎖很少用,加表鎖的語法:
LOCK TABLES xxx READ:這是加表級共享鎖
LOCK TABLES xxx WRITE:這是加表級獨占鎖

  1. 一個事務對表進行 增刪改操作的時候會加獨占鎖,讀操作的時候回加共享鎖,他們都是表級的意向鎖,實際上沒啥用

62 案例實戰:線上數據庫不確定性的性能抖動優化實踐(上)

  1. 執行一個sql語句,需要加載大量的緩存頁,然后可能導致原來大量的臟頁刷回磁盤中 來騰出空間,這個過程可能會持續幾十毫秒到幾秒,然后sql語句才能執行,從而出現性能抖動。
  1. 磁盤中的redo log文件有兩個,第一個寫滿了 寫第二個,第二寫滿了寫第一個,如此往復。
    但是循環向第一個寫的時候 要先看看對應的緩存頁是否都刷入了磁盤,沒有的話就要刷入,此時也會造成性能波動。

63 案例實戰:線上數據庫莫名其妙的隨機性能抖動優化(下)

  1. 解決上面這兩種問題 有兩種思路,一種是減少緩存頁flush到磁盤的頻率,第二個是提升緩存頁flush到磁盤的速度,就是控制頻率和速度。
  1. 控制頻率 只有加大機器內存,其它就很難控制了
  1. 控制速度,將緩存頁flush到磁盤的耗時控制到最小,解決方案如下:
    使用SSD:它的隨機讀寫速度較高
    innodb_io_capacity:該參數控制了 使用多的I/O速率將緩存頁flush 到磁盤中
    innodb_flush_neighbors:設置為0 代表不加載相鄰的緩存頁。

六、索引

64 深入研究索引之前,先來看看磁盤數據頁的存儲結構

  1. 每個數據頁直接看做一段在磁盤中連續的數據,他存儲了有上一個數據頁的地址,和下一個數據頁的地址,構成了一個雙向鏈表,然后每個數據頁中存儲的數據行,他們直接構成單鏈表

65 假設沒有任何索引,數據庫是如何根據查詢語句搜索數據的?

  1. 數據行被分到不同的槽位中,每個數據頁包含 頁目錄,存放了主鍵和槽位的對應關系
  1. 如果根據主鍵查數據,直接在數據頁中做二分查找,找到對應的槽位,再找到槽位中對應的行。
  1. 而如果是非主鍵·則無法使用二分查找,只能進到數據頁中對單向鏈表進行遍歷查找。
  1. 從第一個數據頁開始,而且要先加載成緩存頁,如果第一個沒找到就繼續加載后面的數據頁,而且都要先加載成緩存頁
  1. 以上過程就是全表掃描,效率很低

66 不斷在表中插入數據時,物理存儲是如何進行頁分裂的?

  1. 插入的數據在數據頁中會構成單列表,前面的數字都是類型,2代表最小的一行,3代表最大的一行

  1. 頁分裂:如果在一個數據頁的數據寫滿了,就要挪到另外一個數據頁,而索引運作的一個機制就是要求 后面的數據頁的主鍵值要比前一個都大,然后如下圖,第一個數據也中 比我大的都要挪到后面這個數據頁。
    這個用來保證后面數據頁比前面數據頁主鍵大的, 數據挪動的過程就是頁分裂。

67 基于主鍵的索引是如何設計的,以及如何根據主鍵索引查詢?

  1. 可以看成主鍵索引的 主鍵目錄: 主鍵查找肯定不能走全表掃描,于是有了主鍵目錄,他有每頁的頁號 和最小的主鍵值,這樣就可以通過二分查找來進行快速的查找了。

68 索引的頁存儲物理結構,是如何用B +樹來實現的?

  1. 主鍵目錄->索引頁:一個主鍵目錄 不能面對大量數據,存儲大量的最小主鍵值。
    于是就采用了 將索引數據存放到數據也中的方式,然后就會形成很多的索引頁。

  1. 數據分了多個數據頁,而索引多的時候也要分成索引頁,而很多索引頁最后頁堆成了一個樹的結構

69 更新數據的時候,自動維護的聚簇索引到底是什么?

  1. 從索引樹的頂層開始查找,最后查到索引頁59號,然后找到對應的數據頁
  1. 每個數據頁對應了一個頁目錄,然后就是會在這個頁目錄里面找到對應的槽。
  1. 最下面的索引頁也引用了數據頁,同一級的索引頁之間有雙向鏈表。
  1. 整體來看,索引頁和數據頁構成了一整棵B+樹,數據頁作為葉子節點,這樣這顆B+樹索引就叫做聚簇索引
  1. 頁分裂過程會保證 后面的數據頁的最小主鍵值 比前面數據頁里面的所有主鍵值都大
  1. 如果一個數據頁越來越多,索引頁放不下,就會拉出新的索引頁,同時在搞一個上層的索引頁。
    而一般情況下億級別的大表索引的層級也就三四層。
  1. 從聚簇節點的根節點開始進行二分查找,找到對應的數據頁,再基于頁目錄 定位到主鍵對應的數據

70 針對主鍵之外的字段建立的二級索引,又是如何運作的?

  1. 現有sql語句select * from table where name =‘張三’, 對name建立索引,下面將對其進行分析。
  1. name字段的索引B+樹:其它字段建立索引,比如name字段,插入數據的時候會新建一個B+樹,這個B樹的葉子節點也是數據頁,與聚簇索引B+樹不同的是,前者數據頁只存放了主鍵和Name字段,后者則存放了一整行數據。
  1. 搜索過程是通過排好序的 name值進行二分查找,但查到數據頁的時候只能獲得主鍵值,所以還要用主鍵值,到聚簇索引中獲得其它數據,這個過程叫回表。而name這種普通索引稱之為二級索引。一級索引就是聚簇索引。

    的B +樹的?
  1. 新建一個表:就是一個數據頁,它是空的,并且屬于聚簇索引的一部分。
  1. 插入數據:并且維護了一個頁目錄,根據主鍵搜索沒問題,這個初始的數據頁就叫根頁
  1. 插入更多數據:一個數據頁放不下,新建一個數據頁,把根頁數據都拷貝過去,再新建一個,根據主鍵值大小進行挪動
  1. 此時的根頁:成為了索引頁,里面存放了 兩個數據頁的最小主鍵值和數據頁的頁號
  1. 繼續增多:數據頁繼續分裂,索引頁不斷增多,索引頁也會繼續分裂,成為一顆B+樹
  1. Name索引的注意點:name索引的分裂過程和上面相似,需要注意的就是 它的索引頁里面也存放了主鍵,因為同一級的name字段值 可能一樣并且 指向不同的頁號,這個時候就要根據主鍵判斷了

72 一個表里是不是索引搞的越多越好?那你就大錯特錯了!

  1. 索引的順序性:索引頁內部的數據是按照從小到大的順序 組成單向鏈表,數據頁之間還有索引頁之間構成雙向鏈表,也是有序的。
    最終可以使用二分查找,效率很高。
  1. 使用索引的缺點:
    一是占用磁盤空間,二是增刪改數據還要維護索引

73 通過一步一圖來深入理解聯合索引查詢原理以及全值匹配規則

  1. 對于聯合索引來說,就是依次按照各個字段來進行二分查找(比如name+age+gender,先根據name二分查,不是再找age)
  1. 全值匹配:就是搜索條件與索引一 一對應,百分之百用上索引。

74 再來看看幾個最常見和最基本的索引使用規則

  1. 最左匹配原則:(class_name,student_name, subject_name),從左邊按照順序匹配,如果查詢條件是
    where class_name = ? and subject_name,那么后面的subject_name是沒法作為索引查的。
  1. 最左前綴匹配原則:百分號不能在前,在前的話沒法在索引中定位。
  1. 范圍查找規則:where class_name>'1班' and class_name<'5班',可以使用索引,但是student_name就不可以,因為范圍查找 只能對聯合索引最左側的列才能生效
  1. 等值匹配+范圍匹配:where class_name='1班' and student_name>'' and subject_name<'', student_name走索引,但是subject_name不行。

75 當我們在SQL里進行排序的時候,如何才能使用索引 ?

  1. 直接在內存中排序肯定不行,數據量大的話,在磁盤中直接上排序算法也會要了老命。
  1. 對于一個聯合索引 INDEX(xx1,xx2,xx3),他本來就已經排好序了
  1. 規則: order by條件里 不能有的升序有的降序,那樣不走索引, 而且條件里面有不在索引里面的 也完蛋,帶函數的也完蛋。

76 當我們在SQL里進行分組的時候,如何才能使用索引?

  1. group by與order by一樣,也是從索引的最左側開始進行匹配,
  1. 如果能利用上索引 ?就不再需要針對雜亂無章的數據 利用內存在進行重拍和分組了。

77 回表查詢對性能的損害以及覆蓋索引是什么 ?

  1. 對于select * 這種語句,他需要掃描聚簇索引和聯合索引,有時候mysql可能會認為還不如直接走全表掃描,
    但是如果加上limit數量限制還是會走聯合索引的。
  1. 覆蓋索引的概念:指在索引樹中就可以直接查詢到要的數據

78 設計索引的時候,我們一般要考慮哪些因素呢? (上)

  1. 在系統開發好SQL語句都寫好后,就根據索引的設計原則去加索引。
  1. 設計的索引最好要包括 ?where、order by、group by里面的條件,而且要注意順序,符合最左原則。

79 設計索引的時候,我們一般要考慮哪些因素呢? (中)

  1. 基數比較大(就是取值要種類要多)的字段:那樣才能發揮B樹的潛力(關鍵)
  1. 考慮字段類型的大小:字段類型盡量小,比如tinyint,而如果對varchar這種類型簡歷索引,可以考慮只取前面一部分內容。建立出來的索引如:KEY my_index(name(20),age,course),這個就是前綴索引,但是他沒辦法用到group by和order by中。

80 設計索引的時候,我們一般要考慮哪些因素呢? (下)

  1. 查詢條件中不要放函數: 如where function(a) = xx,不走索引
  1. 索引不要太多:建立兩三個覆蓋索引,覆蓋所有查詢
  1. 不要使用UUID做主鍵:主鍵值都是有序的,這樣搞會頻繁導致頁分裂

81 案例實戰:陌生人社交APP的MySQL索引設計實戰(一)

  1. 實際場景中 where使用索引 和order by使用索引不可兼得

82 案例實戰:陌生人社交APP的MySQL索引設計實戰(二)

  1. 一般優先 滿足where條件使用索引的需求,篩選出來的數據再進行排序
  1. 基數較低也不一定 就不用索引!
    比如我要建立索引:(province, city, sex),雖然基數較低,但是如果不建立索引每次就要先用其它索引查一遍,加載到內存中,然后再根據這幾個條件過濾一遍。所以還不如直接放到聯合索引的最左側。

83 案例實戰:陌生人社交APP的MySQL索引設計實戰(三)

  1. 中間缺了個條件怎么辦?
    對于索引 (province, city, sex), 我要查where province=xx and city=xx and age between xx and xx, 此時可以把索引建成 (province, city, sex,age),查詢是 還把sex帶上,只不過取所有的,where province=xx and city=xx and sex in ('female', 'male') and age >=xx and age<=xx
  1. 對于一些其它的枚舉值,比如性格 愛好,取值類型不多,索引可以設計成(province, city, sex, hobby, character, age)
    查詢語句寫成:where province=xx and city=xx and sex in(xx, xx) and hobby in (xx, xx, xx, xx) and character=xx and age>=xx and age<=xx
    就是說 如果不需要根據某一個條件進行過濾 那就直接in 里面放所有
  1. 范圍查詢的字段的索引,要放到最后,如果放到中間的話,后面的字段的索引也就用不上了。

84 案例實戰:陌生人社交APP的MySQL索引設計實戰(四)

  1. 如果要再加一個條件,最近7天內登陸過的,latest_login_time <= 7,這個地方還會用到函數,而且放在age后面也不會走索引,此時可以加一個枚舉值字段,用來直接表示最近天是否登陸過,does_login_in_latest_7_days,接下來建索引就可以把他放到age的前面了
    (province, city, sex, hobby, character,does_login_in_latest_7_days, age )
  1. 輔助索引在 低基數條件、有排序分頁的情況下的運用:
    對于SQL語句 select xx from user_info where sex='female' order by score limit xx,xx,
    對此設計索引(sex, score),雖然

85 提綱挈領的告訴你,SQL 語句的執行計劃和性能優化有什么關系?

執行計劃:提交一個SQL給給MySQL,然后查詢優化器 會生成一個執行計劃,執行計劃代表具體的怎么查詢

86 以MySQL單表查詢來舉例,看看執行計劃包含哪些內容(1) ?

  1. const: const就是超高性能的查,就是直接從聚簇索引或者聚簇索引+二級索引(唯一類型的)查得的,速度極快
  1. ref:普通的二級索引查詢,包括了從左查詢覆蓋的索引,而對于name IS NULL的這種查詢,因為判空函數,所以她不會是const,而是ref_or_null

87 以MySQL單表查詢來舉例,看看執行計劃包含哪些內容(2) ?

  1. Range:利用了返回查找的普通索引就是range
  1. Index:對于索引KEY(x1,x2,x3), 查詢語句select x1,x2,x3 from table where x2=xxx,索引樹中的葉子節點 存儲的是這三個值+主鍵,這種慢于上面三種,需要遍歷二級索引,但強于遍歷聚簇索引(全表掃描)
  1. All:最次的一種全表掃描

88 再次重溫寫出各種SQL語句的時候,會用什么執行計劃? (1)

  1. 對于:
select * from table where x1=xx or x2>=xx
--索引
(x1,x3),(x2,x4)


查詢優化器 會選擇行數比較少的,如 x1 == XX

  1. 對于x1=xx and c1=xx and c2>=xx and c3 IS NOT NULL 這種查詢,可能就只能走 x1一個索引,不肯能為每個條件都加行索引,所以就只能盡量保證 ?x1篩選出來的數據盡量少

89 再次重溫寫出各種SQL語句的時候,會用什么執行計劃? (2)

  1. 同時使用兩個索引的情況select * from table where x1=xx and x2=xx,先對x1進行索引查找,再對x2進行索引查找,最后取交集。
  1. 上面這種查詢 比先查X1 然后回表,然后再根據X2過濾要好的多
  1. 出現這種情況的條件是:如果有聯合索引 那么這個聯合索引 需要時全值全匹配,或者是主鍵+其它二級索引等值全匹配,這樣才會出現多索引查詢做交集。

90 再次重溫寫出各種SQL語句的時候,會用什么執行計劃? (3)

總結

91 深入探索多表關聯的SQL語句到底是如何執行的? (1)

  1. 對于查詢select * from t1,t2 where t1.x1=xxx and t1.x2=t2.x2 and t2.x3=xxx
    它是先從t1表過濾,然后t2表過濾,如果加了索引 二者各干各的,然后讓t1表去關聯t2表中的數據
  1. 其中先查一波數據 這個叫做驅動表,然后去關聯另一張表里面的數據 他就是被驅動表

92 深入探索多表關聯的SQL語句到底是如何執行的? (2)

  1. 內外連接的基本語義

93 深入探索多表關聯的SQL語句到底是如何執行的? (3)

  1. 嵌套循環關聯(nested-loop join): 就是比如一個表里面查出來10條數據,然后就得讓這10條數據 循環匹配表二中的數據,如果索引沒有加好 效率就會很低下
t1Rows = queryFromt1() // 根據篩選條件對t1標進行查詢
for t1Row in t1Rows { // 對t1里每一條符合條件的數據進行循環t2Rows = queryFromt2(t1Row) //拿t1里的數據去t2表里查詢以及做關聯for t2Row in t2Rows { // 對t1和t2關聯后的數據進行循環t3Rows = queryFromt3(t2Row) // 拿t1和t2關聯后的數據去t3表里查間和關聯for t3Row in t3Rows { 1// 遍歷最終t1和t2和t3關聯好的數據}}
}

94 MySQL是如何根據成本優化選擇執行計劃的? ( 上)

  1. I/O 成本 1.0 :主要指從磁盤中讀取數據所需的成本,1.0是自定義的一個值
  1. CPU成本 0.2 :主要是指對數據的處理,0.2是自定義值。
  1. 計算成本方法:
-- 執行命令 返回的 rows是inndb的估計值,還有data_length
show table status like "表名"
-- 計算有多少數據頁
data_length / 16 = ()kb
-- I/O成本值計算
數據頁數量 * 1.0 + 微調值
-- CPU成本值計算
行記錄數 * 0.2 + 微調值
-- 然后總值相加即可
總成本 = I/O+Cpu

95 MySQL是如何根據成本優化選擇執行計劃的? (中)

  1. 根據二級索引 先刷新到I/O中,一般的等值查詢 和范圍查詢較少的 可以直接認為是 1*1.0 或者 n**1.0
  1. 拿出的數據 還要經過過濾處理,估算拿到100條數據,那么cpu成本就是 100**0.2+微調值
  1. 然后再回表去聚簇索引中查找完整數據(100 *1+微調值),然后再對數據進行過濾處理(100 * 0.2 + 微調值)
  1. 最后總的成本就是 1 + 20 + 100 + 20 = 141
  1. 執行計劃 根據最小的成本值去執行
  1. 這種成本不是精確計算 而是大致計算的。

96 MySQL是如何根據成本優化選擇執行計劃的? (下)

  1. 多個表的關聯查詢成本計算

97 MySQL如何基于各種規則去優化執行計劃的? (上)

  1. 常量替換
    i = 5 and j > i這樣的SQL,就會改寫為i = 5 and j > 5
  1. select * from t1 join t2 on t1.x1=t2.x1 and t1.id=1
    改寫為
    select t1表中id=1的那行數據的各個字段的常量值, t2.* from t1 join t2 on t1表里x1字段的常量值 =t2.x1

98 MySQL如何基于各種規則去優化執行計劃的? (中)

  1. 對于 select * from t1 where x1 = (select x1 from t2 where id=xxx),它是先查詢 括號內語句,然后就相當于普通的連表查了
  1. 另外一種效率則較低 select * from t1 where x1 = (select x1 from t2 where t1.x2=t2.x2),子查詢用到了外表的字段,

99 MySQl是如何基于各種規則去優化執行計劃的? (下)

  1. simi join 查詢:將select * from t1 where x1 in (select x2 from t2 where x3=xxx)
    優化成select t1.* from t1 semi join t2 on t1.x1=t2.x2 and t2.x3=xxx,其中semi join是mysql內部的一種用法

100~108 透徹研究通過explain命令得到的SQL執行計劃

explain 開篇 (100)

  1. explain 命令:SQL前面加一個 explain explain select * from table
  1. 如果是簡單的 sql語句那么就會查出來一行,如果是復雜的sql語句 就會是多行,因為執行計劃包含了多個步驟。

  1. 其中
    id:每次查詢計劃的Id
    select_type ?: 查詢類型
    table:表名稱
    partitions :分區
    type ?: 當前對這個表的訪問方法,就好比之前的index all等
    possible_keys ?: 可能使用的索引
    key_len:索引的長度
    ref:使用某個字段的索引進行等值匹配搜索的時候,跟索引列進行等值匹配的那個目標值的一些信

    rows:是預估通過索引或者別的方式訪問這個表的時候,大概可能會讀取多少條數據。
    filtered:就是經過搜索條件過濾之后的剩余數據的百分比。extra是一些額外的信息,不是太重要。

簡單說明:

  1. explain select * from t1


    id:
    select_type ?: simple 表示一個簡單的查詢
    table:t1表
    partitions :分區
    type ?: 沒加任何條件就是all
    rows:查出來大概 3457條數據
    filtered:100%全查出來了
  1. explain select * from t1 join t2


    先是表1 all掃描
    extra:Nested Loop ?代表嵌套執行
    上面的這個用的是同一個查詢計劃,id都一樣,如果有子查詢id則會等于二
  1. EXPLAIN SELECT * FROM t1 WHERE x1 IN (SELECT x1 FROM t2) OR x3 = 'xxxx';


    因為有兩個select 所以id不同
    第一條:select_type是PRIMARY ?代表主查詢
    第二條:select_type是SUBQUERY ?代表子查詢
  1. EXPLAIN SELECT * FROM t1 UNION SELECT * FROM t2


    主要是第三個查詢計劃,他干的就是去重的活,<union 1,2> ?是一個臨時的表名,extra中有一個 using
    temporary ?代表使用臨時表。
  1. join buffer: ? EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.x2 = t2.x2
    如果兩個表都沒有用索引,會使用此技術 在內存中做一些優化 減少t2表的全表掃描次數

  1. 如果排序條件沒有走 索引那么extra就會是 filtered,性能比較差,直接在內存中進行的排序,如果是分組 distinct操作 則會是temporary 效率同樣非常低

109~117 案例實戰

千萬級用戶場景下的運營系統SQL調優

  1. 需求描述:
    用戶量是百萬級別的,現在要根據一些條件 取一些用戶做一些消息推送 這樣的業務需求
  1. SQL語句:
    SELECT id, name FROM users WHERE id IN (SELECT user_id FROM users_extent_info WHERE latest_login_time < xxxxx)
    sql中除掉基礎的用戶表之外還有 用戶信息的擴展表,通常這種查詢要先看下數據量,前面可以改寫為SELECT count(id) 如果超過1000 那么是可以用limit 進行限制,每次取出來1000條數據,多分幾次推送消息。 但是上面的這種sql語句 在千萬級的表中還是會跑出來幾十秒的耗時,不可行的。
  1. 執行計劃分析:
+----+-------------+-------+------------+-------+---------------+----------+---------+------+
| id | select_type | table | type | key | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+
| 1 | SIMPLE | | ALL | NULL | NULL | 100.00 | NULL |
| 1 | SIMPLE | users | ALL | NULL | 49651 | 10.00 | Using where; Using join buffer(Block Nested Loop) |
| 2 | MATERIALIZED | users_extent_info | range | idx_login_time | 4561 | 100.00 | NULL |


先看第三行: 針對latest_login_time 做 range類型的索引 ,MATERIALIZED表明是將結果集物化成一個臨時表,落到磁盤中 速度較慢。
第一條執行計劃是物化表的,第二條是users表和第一個表做join的,所以會有using join buffer, 并且filter 是10%

  1. 為什么會這么慢:
    第一點:將臨時表幾千條數據物化出來,而是users表需要和這個物化出來的表 做全表掃描,所以很慢
    第二點:使用命令show warnings,會看到 semi join這個關鍵字,他也是原因
  1. 怎么優化:
    什么是semi join:只要users表里的一條數據,在物化臨時表里可以找到匹配的數據,那么users表里的數據就會返回,這就叫做semi join,他是用來篩選的。
    執行 SET optimizer_switch='semijoin=off 速度就會提升 但一般我們不能在生產環境這么做
    改變sql語句 來反正半連接:
-- 時間上后面的那個or條件 是沒用的
SELECT COUNT(id)
FROM users
WHERE (id IN (SELECT user_id FROM users_extent_info WHERE latest_login_time < xxxxx) OR id IN (SELECT user_id FROM users_extent_info WHERE latest_login_time < -1)
)

  1. 都用上索引其實才是王道

億級數據量商品系統的SQL調優實戰

  1. 場景和需求:
    晚高峰,商品系統非常繁忙,TRS每秒幾千,每分鐘的慢查詢超過了 10w+,數據庫連接阻塞,用戶無法查看商品相關的內容
  1. SQL語句
    就是根據一些條件查詢商品信息,但他查詢需要幾十秒,數據庫連接被打滿
SELECT* 
FROMproducts 
WHEREcategory = 'xx' AND sub_category = 'xx' 
ORDER BYid DESC LIMIT xx,xx

  1. 原因分析:
    如果是正常走索引,億級表也不過1s鐘,查看執行計劃 發現possible_keys ?的確是category ?索引,但是實際上用的key卻不是,而且extra里面寫了 using where
    救急 使用force index:
    select * from products force index(index_category) where category='xx' and sub_category='xx' order by id desc limit xx,xx
    產生的問題 由此產生了下面的三個問題
  1. 為什么在這個案例中MySQL默認會選擇對主鍵的聚簇索引進行掃描?
    為什么沒使用index_category這個二級索引進行掃描?
    mysql擔心 從二級索引拿出來的數據會過多,而且還可能需要在內中排序,而且你是select * ,需要進行回表,那么我還不如直接走聚簇索引,using where
    其實走聚簇索引也不慢,這里也是因為看到你 limit 10, 正常掃描聚簇索引也應該不會超過1s起碼
  1. 即使用了聚簇索引,為什么這個SQL以前沒有問題,現在突然就有問題了?
    這個就因為 有些商品分類并沒有對應的商品,就是說使用category進行搜索的時候 有些數據找不到,這就導致sql會做全表掃描,而且掃來掃去也找不到數據

數十億數量級評論系統的SQL調優實戰

  1. 對于一件熱銷的商品,它的銷量有幾百萬,評論多達幾十萬,用戶可任意翻找評論,總結一下就是
    針對一個商品幾十萬評論的深分頁問題。
  1. 查詢語句:SELECT * FROM comments WHERE product_id ='xx' and is_good_comment='1' ORDER BY id desc LIMIT 100000,20
    此語句的問題:
    如果只走了一個index_product_id 索引,里面沒有is_good_comment,那就要對查出來的幾十萬條數據每一條數據 都要回表去比對 is_good_comment,總之就是他不適合用二級索引去查詢,因為回表次數過多。
    進行改造:SELECT * from comments a,(SELECT id FROM comments WHERE product_id ='xx' and is_good_comment='1' ORDER BY id desc LIMIT 100000,20) b WHERE a.id=b.id
    這個sql語句會先執行 子查詢,他會走聚簇索引,按照主鍵倒序排列,然后找到符合條件的20條數據,然后這20條數據作為結果集,回表去聚簇中查一次就行了。

千萬級數據刪除導致的慢查詢優化實踐 115~117&120

  1. 情況描述:發現了大量慢查詢,而查看語句發現 它不應該會導致慢查詢,可以推測是Mysql服務本身的問題,如磁盤的I/O負載比較高,或者網絡的負載超高,還有就是CPU負載,CPU過于繁忙,然后排查,方向也并不是這里的問題,這是就需要用profilling工具去排查
  1. profiling(剖析) 工具:
-- 開始profiling
set profiling = 1
-- 從返回結果中可以看到 查詢的語句,對應的id和耗時
show profiles
-- 其中查詢出來的結果里面有一個send_data
show profile cpu, block io for query 16

  1. 分析結果
    使用剖析工具查詢出來的 send_data 耗時很高,然后使用show engine innodb status發現 history list length 很高,到了上萬,它就是undo log版本鏈條的長度,事務提交后就應該被 purge清理掉,而這個list很長就代表 有事務長時間沒有提交
  1. 原因 和解決方法
    原因就是那個時候在清理上千萬條數據,事務一直沒提交掉,而且對于刪除只是加了個標記,這樣查詢的時候就導致沒查到 就走全表掃描
    然后對于長事務而言,這么多數據,每次查詢的時候還會生成大量的readView,而且這刪除的幾千萬條數據 還都會生成 undo log鏈條,這也就解釋了 history list為什么那么大,而且一直都不被purge清理掉。

118 ~119 我們為什么要搭建一套MySQL的主從復制架構

  1. 主從架構:主節點和從節點數據保持一致,一旦主節點掛掉那么從節點還可以提供服務
    這就是它的第一個作用:保證高可用

  1. 主從架構的第二個作用:可以作為讀寫分離架構
    比如讀寫 各有3000 的qps(一般讀比寫高的多),一臺機器只能承載5000,此時就可以使用讀寫分離,而且可以添加多個從節點,其中還可以融入中間件 來實現故障轉移等功能。

主從復制的基本原理

  1. binlog中記錄了所有的增刪改操作。
  1. 主機點就負責生成binlog,然后用dump線程把日志傳輸給從庫,從庫就使用I/O線程把日志寫入到本地的relay中去,然后從庫會再基于它 重新把sql語句執行一遍,來達到一個數據同步的效果,當然數據并不是完全一致的。

121 ~123 如何為MySQL搭建一套主從復制架構?

創建簡單的 主從庫 (異步 數據同步有延遲)

  1. 主庫創建一個用于主從復制的賬號
  1. 不能在主庫還在提供服務的時候 ?讓從庫從零開始導入數據,要選擇一個時間進行維護,導入和數據備份。
  1. 使用mysql自帶的 msqldump工具 進行備份
    /usr/local/mysql/bin/mysqldump --single-transaction -uroot -proot --master-data=2 -A > backup.sql
    然后就把這個 backup.sql 復制到從庫里面,在從庫執行他,
  1. CHANGE MASTER TO MASTER_HOST='192.168.31.229', MASTER_USER='backup_user',MASTER_PASSWORD='backup_123',MASTER_LOG_FILE='mysqlbin.000015',MASTER_LOG_POS=1689;

半同步的 主從庫復制(一般采用這種方式)

  1. 半同步有兩種方式
    一種是after_commit,非默認主庫將binlog 復制到從庫之后,提交事務,然后等待從庫的響應,然后再講主庫提交事務成功的消息給從庫
    另一種是 mysql 5.7默認的方式:主庫將binlog 給從庫,然后等待從庫相應成功,然后再提交事務,并把提交成功的消息告訴從庫
  1. 半同步復制 只需安裝插件,然后可以再配合高可用切換機制,就可以實現數據庫的高可用

GTID 搭建方式(較為簡便)

  1. 主從庫想要配置下配置文件 ?主要是server_id,其余配置就和簡單搭建一樣了
  1. 然后可以配合 mycat中間件和sharding-sphere 中間件來實現 主從復制 都沒問題

124 主從復制架構中的數據延遲問題,應該如何解決?

  1. 延遲問題的產生:比如多個線程向主庫中寫入,從庫單個線程拉取數據,這個問題如果是讀寫分離的話就會導致寫進去的數據 會有一會兒讀不出來。
  1. 如何解決:
    mysql 5.7支持的并行復制:在從庫中設置 slave_parallel_workers>0 slave_parallel_type設置為LOGICAL_CLOCK
  1. 如果需要數據要能被立刻讀取到的話 可以使用MyCat或者Sharding-Sphere之類的中間件里設置強制讀寫都從主庫走。

125 ~127 ?數據庫高可用:基于主從復制實現故障轉移

  1. 所謂靠可用 的核心就是要有故障轉移,主庫掛了要能立刻把從庫切換為主庫】、
  1. 使用工具MHA(Master High Availability Manager and Tools for MySQL) ,它探測到節點掛掉之后就立刻搞出來新的節點作為主節點
  1. 搭建過程 。。。。

128 案例實戰:大型電商網站的上億數據量的用戶表如何進行水平拆分?

  1. 背景 :數據量大,單表搜索扛不住
  1. 建議:單表不超過1000w,最好不超過500w, 100w是最佳的選擇,幾千萬的用戶數據 也就幾個gb,可以讓一臺服務器 放兩個庫
  1. 數據庫分發:
    一般是用 數據庫id來取模,但是要用用戶名來查的話 ?就要建立一個專門存儲用戶名和用戶id的表,但這樣相當于查兩次,所以也可以用es來做用戶的復雜查詢(監聽binlog)

129 案例實戰: 一線電商公司的訂單系統是如何進行數據庫設計的?

  1. 背景: 用戶去查自己的訂單
  1. 設計方案: userid和orderid做一個映射放在表里,并且是根據userid來做分表,最終拿到的orderid 再可以去es中查詢完整數據

130 案例實戰:下一個難題,如果需要進行垮庫的分頁操作,應該怎么來做?

  1. 盡量不要搞,可以用es 或者在 userid和orderid的那張表里面放上要查詢的條件

131 案例實戰:當分庫分表技術方案運行幾年過后,再次進行擴容應該怎么做

在開始的時候多增加表,擴容的時候就括服務器,把數據庫遷移到上面去就行了

132 專欄總結:撒花慶祝大家對數據庫技術的掌握更進一步

很好,就是后面的主從復制、高可用、分庫分表這些講的比較初級

★,°:.☆( ̄▽ ̄)/$:.°★

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

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

相關文章

Git 詳解:從概念,常用命令,版本回退到工作流

本文將從 Git 的核心概念講起&#xff0c;詳細介紹常用命令、各階段版本回退、分支控制以及企業內常見的 Git 工作流。 Git 與 GitHub 簡介 Git 簡介 Git 是一個開源的分布式版本控制系統&#xff0c;由 Linus Torvalds 于 2005 年開發。它與集中式版本控制系統&#xff08;…

CMSIS(Cortex Microcontroller Software Interface Standard)ARM公司為 Cortex-M 系列處理器

CMSIS&#xff08;Cortex Microcontroller Software Interface Standard&#xff09;是ARM公司為 Cortex-M 系列處理器&#xff08;如 M0/M3/M4/M7/M23/M33 等&#xff09;定義的一套硬件抽象層標準&#xff0c;旨在簡化嵌入式開發&#xff0c;提高代碼的可移植性和復用性。 核…

[特殊字符] 掃描式處理:Python 自動提取 PDF 中關鍵詞相關表格并導出為 Excel

本文演示如何利用 pdfplumber 批量處理指定文件夾下 PDF 文檔&#xff1a;定位關鍵詞&#xff08;如“主要會計數據”&#xff09;出現的頁碼及下一頁&#xff0c;提取其中的表格并保存為獨立 Excel 文件。適用于財務報告、審計表格、統計報表等場景。 1?? 第一步&#xff1a…

python3的返回值能返回多個嗎?

在Python中&#xff0c;函數可以通過返回一個元組&#xff08;tuple&#xff09; 來間接實現返回多個值的效果。以下是具體說明&#xff1a; 實現方式&#xff1a;直接返回逗號分隔的值 Python會自動將這些值打包成一個元組&#xff1a; def multiple_return():a 1b "he…

UE5 Secondary Materials

首先放入材質A材質B放入Secondary Materials兩個效果就能融合到一起了動態設置secondary material

AUTOSAR進階圖解==>AUTOSAR_SWS_FlashTest

AUTOSAR Flash Test模塊詳解與分析 基于AUTOSAR標準的Flash Test模塊架構、功能與應用分析目錄 1. Flash Test模塊概述 1.1 模塊作用與功能1.2 適用范圍 2. Flash Test模塊架構 2.1 模塊位置2.2 組件關系 3. 狀態管理 3.1 狀態定義3.2 狀態轉換 4. 后臺測試執行流程 4.1 測試間…

msf復現永恒之藍

永恒之藍&#xff08;EternalBlue&#xff09;是利用 Windows 系統的 SMB 協議漏洞&#xff08;MS17-010&#xff09;來獲取系統最高權限的漏洞&#xff0c;利用 Metasploit 框架&#xff08;MSF&#xff09;復現該漏洞是一個復雜且具有一定風險的操作&#xff0c;必須在合法合…

格密碼--LWE,DLWE和ss-LWE

格密碼–LWE&#xff0c;DLWE和ss-LWE 0.數學符號數學符號含義備注Zq\mathbb{Z}_qZq?模qqq的整數集合&#xff0c;即{0,1,2,...,q?1}\{0,1,2,...,q-1\}{0,1,2,...,q?1}用于定義LWE、DLWE、ss-LWE等問題中矩陣和向量的元素取值范圍&#xff0c;是基礎整數環x∈RSx \in_R Sx∈…

【閉包】前端的“保護神”——閉包詳解+底層原理

目錄 一、閉包是什么&#xff1f;概念 二、閉包為什么存在&#xff1f;作用 1. 創建私有變量 2. 實現數據封裝與信息隱藏 3. 模擬私有方法 4. 保存函數執行時的狀態 5. 回調函數和事件處理 6. 模塊化編程 7. 懶加載與延遲執行 三、閉包怎么用&#xff1f;實踐業務場景 …

算法學習筆記:19.牛頓迭代法——從原理到實戰,涵蓋 LeetCode 與考研 408 例題

牛頓迭代法&#xff08;Newtons Method&#xff09;是一種強大的數值計算方法&#xff0c;由英國數學家艾薩克?牛頓提出。它通過不斷迭代逼近方程的根&#xff0c;具有收斂速度快、適用范圍廣的特點&#xff0c;在科學計算、工程模擬、計算機圖形學等領域有著廣泛應用。牛頓迭…

小白學Python,操作文件和文件夾

目錄 前言 一、操作文件路徑 1.獲取當前路徑 2.創建文件夾 &#xff08;1&#xff09;mkdir()函數 &#xff08;2&#xff09;makedirs() 函數 3.拼接路徑 4.跳轉路徑 5.判斷相對路徑和絕對路徑 6.獲取文件路徑和文件名 二、操作文件和文件夾 1.查詢文件大小 2.刪除…

015_引用功能與信息溯源

引用功能與信息溯源 目錄 引用功能概述支持的模型引用類型API使用方法引用格式應用場景最佳實踐 引用功能概述 什么是引用功能 Claude的引用功能允許在回答基于文檔的問題時提供詳細的信息來源引用&#xff0c;幫助用戶追蹤和驗證信息的準確性。這個功能特別適用于需要高可…

ROS2中的QoS(Quality of Service)詳解

ROS2中的QoS&#xff08;Quality of Service&#xff09;詳解1. 主要QoS參數2. 為什么需要設置QoS3. QoS兼容性規則4. 選擇QoS策略的建議5. 調試QoS問題的方法6. 踩坑&#xff1a;訂閱話題沒有輸出可能的原因&#xff1a;調試方法QoS是ROS2中用于控制通信質量和行為的機制。它定…

Cursor三大核心AI功能

一&#xff1a;Tab鍵&#xff1a;智能小助手 1.1 單行/多行代碼補全 在代碼中寫出要實現的功能&#xff0c;第一次按Tab生成代碼&#xff0c;第二次按Tab接受代碼。1.2 智能代碼重寫 對已有代碼重新編寫。 寫個注釋告訴AI重構方法&#xff0c;然后鼠標點到方法內部&#xff0c;…

cesium添加原生MVT矢量瓦片方案

項目中需要基于cesium接入mvt格式的服務并支持屬性拾取查詢&#xff0c;通過一系列預研測試&#xff0c;最后選擇cesium-mvt-imagery-provider開源插件完成&#xff0c;關鍵源碼信息如下&#xff1a; npm i cesium cesium-mvt-imagery-provider //安裝依賴包// 加載圖層import…

AI金融風控:識別欺詐,量化風險的新利器

AI金融風控&#xff1a;識別欺詐&#xff0c;量化風險的新利器深度學習算法穿透海量交易數據&#xff0c;92.5%的不良貸款識別率宣告了金融風險防控新時代的來臨。深圳桑達銀絡科技有限公司在2025年6月申請的“基于人工智能的金融交易反欺詐系統”專利&#xff0c;揭示了金融風…

【unitrix】 5.0 第二套類型級二進制數基本結構體(types2.rs)

一、源碼 這是一個使用 Rust 類型系統實現類型級(type-level)二進制數的設計。 //! 類型級二進制數表示方案&#xff08;第二套方案&#xff09; //! //! 使用嵌套泛型結構體表示二進制數&#xff0c;支持整數和小數表示。use crate::sealed::Sealed;/// 類型級二進制數結構體 …

DAY01:【ML 第一彈】機器學習概述

一、三大概念 1.1 人工智能&#xff08;AI&#xff09; Artificial Intelligence 人工智能AI is the field that studies the synthesis and analysis of computational agents that act intelligently 1.2 機器學習&#xff08;ML&#xff09; Machine Learning 機器學習Fi…

AGX Xavier 搭建360環視教程【一、先確認方案】

設備默認自帶 NVIDIA 硬件編解碼能力&#xff08;NVDEC/NVENC&#xff09;&#xff0c;但是需要你在 OpenCV 和 FFmpeg 里正確啟用 調通 GStreamer 或 nvmpi&#xff0c;才真正能用起來&#xff01;這里的硬解碼是核心&#xff1a;Jetson 平臺的硬解碼&#xff0c;要么走 GStr…

服務器怎么跑Python項目?

在服務器上運行 Python 項目通常涉及 環境配置、依賴安裝、項目部署 和 進程管理。以下是詳細步驟&#xff1a;1. 連接服務器確保你能通過 SSH 訪問服務器&#xff1a;ssh usernameyour_server_ip&#xff08;如果是本地測試&#xff0c;可跳過這一步&#xff09;2. 安裝 Pytho…