Mysql數據庫學習彙總

事務的四大特性ACID

原子性(Atomicity): 事務是最小的執行單位,不允許分割。事務的原子性確保動作要麼全部完成,要麼完全不起作用;

一致性(Consistency): 執行事務前後,數據保持一致,多個事務對同一個數據讀取的結果是相同的;

隔離性(Isolation): 併發訪問數據庫時,一個用戶的事務不被其他事務所幹擾,各併發事務之間數據庫是獨立的;

持久性(Durability): 一個事務被提交之後。它對數據庫中數據的改變是持久的,即使數據庫發生故障也不應該對其有任何影響。

併發事務產生的問題

  • 髒讀

  • 不可重複讀

  • 幻讀

具體可以參考我的另外一篇博客

數據庫事物的四種隔離級別

  • Read Uncommitted(讀未提交)

    在該隔離級別,所有事務都可以看到其他未提交事務的執行結果,會產生髒讀的現象。

  • Read Committed(讀已提交)

    只允許事務讀取已經被其他事務提交的變更,雖然避免了髒讀,但是會產生不可重複讀。

  • Repeatable Read(可重讀),這是mysql的默認隔離級別

    確保事務可以多次從一個字段中讀取相同的值,在事務期間,不允許其他事務對這個字段進行修改。可以避免髒讀和不可重複讀,但是會有幻讀問題。

  • Serializable(可串行化)

    確保事務可以從一個表中讀取相同的行,在事務期間,禁止其他事務對對該表執行刪除,修改,添加操作。可以避免所有問題,但是性能底下。

存儲引擎

查看本地mysql的存儲引擎

使用指令show engines;,下圖是我本地mysql的情況

在這裏插入圖片描述

關於上圖幾個參數的解釋

Engine參數指存儲引擎名稱;
Support參數說明MySQL是否支持該類引擎,YES表示支持;
Comment參數指對該引擎的評論;
Transactions 參數表示是否支持事務處理,YES表示支持;
XA參數表示是否分佈式交易處理XA規範,YES表示支持;
Savepoints參數表示是否支持保存點,以便事務回滾到保存點,YES表示支持

一般常用的就兩種,InnoDB和MyISAM

MyISAM :默認表類型,它是基於傳統的ISAM類型,ISAM是Indexed Sequential Access Method (有索引的順序訪問方法) 的縮寫,它是存儲記錄和文件的標準方法。不是事務安全的,而且不支持外鍵,如果執行大量的select,insert MyISAM比較適合。

InnoDB :支持事務安全的引擎,支持外鍵、行鎖、事務是他的最大特點。如果有大量的update和insert,建議使用InnoDB,特別是針對多個併發和QPS較高的情況。注: 在MySQL 5.5之前的版本中,默認的搜索引擎是MyISAM,從MySQL 5.5之後的版本中,默認的搜索引擎變更爲InnoDB。

MyISAM和InnoDB的區別:

  1. InnoDB支持事務,MyISAM不支持。
    對於InnoDB每一條SQL語言都默認封裝成事務,自動提交,這樣會影響速度,所以最好把多條SQL語言放在begin和commit之間,組成一個事務;
  2. InnoDB支持外鍵,而MyISAM不支持。
  3. InnoDB是聚集索引,使用B+Tree作爲索引結構,數據文件是和(主鍵)索引綁在一起的(表數據文件本身就是按B+Tree組織的一個索引結構),必須要有主鍵,通過主鍵索引效率很高。MyISAM是非聚集索引,也是使用B+Tree作爲索引結構,索引和數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的。
  4. InnoDB不保存表的具體行數,執行select count(*) from table時需要全表掃描。而MyISAM用一個變量保存了整個表的行數,執行上述語句時只需要讀出該變量即可,速度很快。
  5. InnoDB不支持全文索引,而MyISAM支持全文索引,查詢效率上MyISAM要高;5.7以後的InnoDB支持全文索引了。
  6. InnoDB支持表、行級鎖(默認),而MyISAM支持表級鎖。
  7. InnoDB表必須有主鍵(用戶沒有指定的話會自己找或生產一個主鍵),而MyISAM可以沒有。
  8. InnoDB存儲文件有frm、ibd,而MyISAM是frm、MYD、MYI。

InnoDB:frm是表定義文件,ibd是數據文件,MyISAM:frm是表定義文件,MYD是數據文件,MYI是索引文件。

InnoDB記錄存儲結構

大神博客

InnoDB採用的是分頁模式,將數據劃分爲若干個頁,以頁作爲磁盤和內存之間交互的基本單位,InnoDB中頁的大小一般爲 16KB。也就是在一般情況下,一次最少從磁盤中讀取16KB的內容到內存中,一次最少把內存中的16KB內容刷新到磁盤中。

我們平時是以記錄爲單位來向表中插入數據的,這些記錄在磁盤上的存放方式也被稱爲行格式。現在基本上存在4中行格式,分別是CompactRedundantDynamicCompressed,就拿我現在使用的數據庫爲例,使用Dynamic作爲行格式。、

在這裏插入圖片描述

Compact行格式

img

我們可以看到,我們存進去的每一條記錄的信息並不是那麼簡單,除了存進去的真實數據之外,mysql還會往記錄中添加一些額外的信息。

變長字段長度列表

針對VARCHAR(M)這類的可變數據類型,我們必須知道所佔的長度,不然在記錄的真實數據我們怎麼知道哪些數據是屬於自己的呢。

注意的是,這裏只存儲變長且爲非null的字段

下面是大神的例子,創建一個表,並添加數據

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-e3cit4B2-1584260905633)(https://i.loli.net/2020/03/15/BKRDW9kHvSLVUts.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-izgPzcba-1584260905639)(https://i.loli.net/2020/03/15/KeZH5C6YyvislDu.png)]

那麼對可變長數據分析如下

列名 存儲內容 內容長度(十進制表示) 內容長度(十六進制表示)
c1 'aaaa' 4 0x04
c2 'bbb' 3 0x03
c4 'd' 1 0x01

最後存儲的就是內容長度,因爲要求必須逆序放置,所以040301就要變成010304

另外原文中還有關於是使用1個字節還有2個字節比埃是內容長度的篇幅,可以區閱讀。

NULL值列表

爲了優化空間,所以設立NULL值列表以表示一條記錄中哪些字段是null。

注意的是,這裏只統計所有列的值允許爲NULL的情況

還是剛纔表的例子,有c1,c3,c4的列是允許爲null的,前面添0是因爲長度要是以字節爲單位,而且還是和前面一樣需要逆序存儲

在這裏插入圖片描述
在這裏插入圖片描述

記錄頭信息

存儲一些記錄數據需要的內容,更多細節可以查看原文

img

記錄的真實數據

除了我們插入的列數據以外,mysql還會插入其他的內容,用於事務的處理

列名 是否必須 佔用空間 描述
row_id 6字節 行ID,唯一標識一條記錄,
只有當不設置主鍵時纔會創立
transaction_id 6字節 事務ID
roll_pointer 7字節 回滾指針

完整的數據

img

還有一點要注意的是非可變長度類型,比如CHAR(10)等等,這個字段如果爲null,那麼就會出現在NULL值列表中,如果不爲空,那麼如果是10字節,那麼就一定會佔10字節空間,空位補空格(0x20)。

Redundant行格式

整體結構

img

同樣的兩條數據的顯示,更多比較請查看原文

img

Dynamic行格式

關於行溢出

我們知道InnoDB按照頁存儲數據,而頁的一般是16KB,也就是16384字節,但是mysql中一條數據存儲的內容很可能高達60000+個字節,(比如一個VARCHAR(M)類型的列就最多可以存儲65532個字節),那麼就會出現在一頁中無法存下一條數據的問題。 那麼對於CompactReduntant這兩種行格式,採用的策略類似於鏈表,就是每一頁只存儲部分數據,然後指定一個地址,表示接下來的內容到這個地址去取。

img

而Dynamic這種格式,就不會在記錄的真實數據處存儲字符串的前768個字節,而是把所有的字節都存儲到其他頁面中,只在記錄的真實數據處存儲其他頁面的地址。

img

Compressed行格式

Compressed行格式和Dynamic不同的一點是,Compressed行格式會把存儲到其他頁面的數據採用壓縮算法進行壓縮,以節省空間。

索引

參考博客一

參考博客二

關於mysql底層爲什麼使用B+樹

一般使用的樹有以下類型

  1. 二叉查找樹
  2. 平衡二叉樹
  3. 紅黑樹
  4. B樹
  5. B+樹

二叉查找樹,時間複雜度明顯不行,在極端情況下會退化

平衡二叉樹,查找性能很好,但是在節點插入和刪除的極端數據下,時間複雜度也不理想,會造成log次旋轉。

紅黑樹,優化了插入和刪除的性能,稍微降級了一些查詢性能,統計性能較好。但是,我們知道mysql數據庫的內容是存在硬盤中的,我們需要將內容讀到內存中才能方便操作。二叉樹的查詢時log2log_2級別的,換句話說,如果紅黑樹有x層,那麼就必須IO操作x次,這大大限制查詢性能,因此也不可取。

B樹,本質上是一棵多叉樹,且叉數>=2,那麼也就是說,一般情況下, B樹的高度會遠小於紅黑樹,減少了IO次數,提高了查詢速度。

B+樹,相較於B樹,擁有比B樹更低的高度,也就意味着更少的IO次數,更快的查詢速度。且B+樹擁有範圍查詢的功能,而這是B樹所沒有的,同時B+樹的查詢的穩定性也更高,一般穩定IO次數是B+樹的高度。

B+樹和B樹的比較

  1. B+樹的層級更少,因爲B+樹的非葉節點只會存儲主鍵和對應的頁號,而B樹的非葉節點會存儲具體的內容,這也就是說,每個節點存儲的記錄個數比B樹多很多,那麼就能夠使得B+樹的高度更低。
  2. B+更適於範圍查詢,在B樹中進行範圍查詢時,首先找到要查找的下限,然後對B樹進行中序遍歷,直到找到查找的上限;而B+樹的範圍查詢,只需要對雙向鏈表進行遍歷即可。
  3. 更穩定的查詢效率,B樹的查詢時間複雜度在1到樹高之間(分別對應記錄在根節點和葉節點),而B+樹的查詢複雜度則穩定爲樹高,因爲所有數據都在葉節點。

放一張B+樹的結構圖

img

索引的幾種類型

聚簇索引

將數據存儲與索引放到了一塊,索引結構的葉子節點保存了行數據

每個非葉子節點保存的是主鍵+頁號,而每個葉子節點存放所有的數據

一個聚簇索引的小問題:

因爲每個InnoDB存儲引擎表都有一個聚簇索引,所以在默認有主鍵的情況下,那麼這個聚簇索引就是主鍵索引,但是在沒有主鍵的情況下,會進行如下操作

  1. 首先判斷表中是否有非空的唯一索引,如果有,則該列即爲主鍵

  2. 如果不符合上述條件,InnoDB存儲引擎自動創建一個6字節大小的指針ROW_ID

現在的問題就在於這個row_id,這個row_id的值是一個全局計數器,這就意味着所有創建row_id的表,他們的插入性能就會受到影響,因爲這個計數器是要線程安全的,以保證主鍵唯一,所以必然這些表在插入的時候是不能併發插入的,所以性能會受到影響。

二級索引(也叫非聚簇索引)

聚簇索引是按照主鍵排序的,那麼如果不想要按照主鍵排序,可以使用二級索引,二級索引簡單來說就是按照某一個非主鍵排序。

每個非葉子節點(也就是目錄項)存放的是對應的非主鍵+頁號,而葉子節點存放的是主鍵+對應的非主鍵。

所以我們如果要利用二級索引獲得完整的數據,還必須再拿獲得的主鍵去查詢聚簇索引。

聯合索引

相較於二級索引,排序的關鍵字不再只有一個,允許可以有多個關鍵字,就相當於是有主關鍵字和副關鍵字一樣,如果主關鍵字不同就按照主關鍵字排序,如果相同,就以副關鍵字排序。

哈希索引

img

哈希索引就是利用哈希算法來創建索引。

那麼特點也就很明顯,單點查詢快,只需要經過一次哈希計算,就能到到對應數據的位置。

缺點也很明顯

  1. 哈希算法的通病,存在哈希衝突
  2. 不能範圍查詢,因爲哈希算法無法保留原有的數據大小關係
  3. 不支持B+樹索引的最左匹配原則
  4. 沒有排序功能

覆蓋索引

覆蓋索引是select的數據列只用從索引中就能夠取得,不必讀取數據行。可以理解爲一種思想,就是查詢列表裏只包含索引列

舉個例子,假設我們創建一個表

CREATE TABLE `tx`.`student` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  `birth` DATE NOT NULL,
  `gender` CHAR(2) NOT NULL,
  `location` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`id`));

那麼我們下面的操作可以稱之爲覆蓋索引

  1. 先建立name,birth,gender的聯合索引
  2. SELECT name,birth,gender from student where name='aaa'使用這樣的查詢語句

InnoDB和MyISAM使用索引的區別

InnoDB一般使用的是聚簇索引+非聚簇索引,既可以通過主鍵直接查找數據,也可以通過非聚簇索引查找主鍵,再通過主鍵查找的方式查詢。

MyISAM的使用的是非聚簇索引,所以對於MyISAM而言,建立按照主鍵排序的索引還是輔助鍵排序的索引在本質上來說都沒有區別,因爲MyISAM維護了一張按照插入順序的表,這裏面存放了所有的數據,且每一條數據都有對應的行號。那麼MyISAM就是通過索引找到對應數據的行號,然後通過行號再去查找所有數據的。

B+樹索引適用的條件

詳情參考這裏

  • 全值匹配
  • 匹配左邊的列
  • 匹配範圍值
  • 精確匹配某一列並範圍匹配另外一列
  • 用於排序
  • 用於分組

建立索引需要注意的事項

  • 只爲用於搜索、排序或分組的列創建索引

  • 爲列的基數大的列創建索引

  • 索引列的類型儘量小

  • 可以只對字符串值的前綴建立索引

  • 只有索引列在比較表達式中單獨出現纔可以適用索引

  • 爲了儘可能少的讓聚簇索引發生頁面分裂和記錄移位的情況,建議讓主鍵擁有AUTO_INCREMENT屬性。

    具體可以參考InnoDB如果沒有主鍵或者隨機主鍵真的很可怕嗎?,這篇文章中的數據

  • 定位並刪除表中的重複和冗餘索引

  • 儘量適用覆蓋索引進行查詢,避免回表帶來的性能損耗。

一些關於索引的面試問題的個人見解

索引爲什麼能夠提高查詢速度?

索引底層使用的是B+樹優化,能夠儘可能的減少磁盤IO次數,節省時間,而且在查詢的時候,由於數據是有序的,因此可以使用二分查找。這樣子就能夠加快查詢速度。如果沒有使用索引,則需要遍歷雙向鏈表,那麼這個操作是非常耗時的。

索引降低增刪改的速度

這個很明顯,索引底層是B+樹,B+樹實際上是平衡樹,既然是平衡樹就需要額外的時間消耗去維護平衡

索引最左匹配原則

對於一個聯合索引,我們當用它去匹配的時候,總是會匹配最左邊的字段,然後相同就會向後在比較一個字段,直到遇到範圍查詢(>、<、between、like)就停止匹配。

比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,只能夠以線性的速度查詢。但是需要注意的是如果查詢語句變成b = 2 and a = 1 and c > 3 and d = 4還是能夠用到a,b的索引。

可以這個樣子原因在於mysql內部有查詢優化器,可以對你的查詢順序進行內部的修改,以達到最大程度利用索引的效果。

鎖的分類

  • 表鎖

    • 開銷小,加鎖快;不會出現死鎖;鎖定力度大,發生鎖衝突概率高,併發度最低
  • 行鎖

    • 開銷大,加鎖慢;會出現死鎖;鎖定粒度小,發生鎖衝突的概率低,併發度高
  • InnoDB行鎖和表鎖都支持

  • MyISAM只支持表鎖

InnoDB只有通過索引條件檢索數據才使用行級鎖,否則,InnoDB將使用表鎖。也就是說,InnoDB的行鎖是基於索引的

行鎖的兩種鎖

共享鎖(S鎖,讀鎖):允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。是共享的,多個客戶可以同時讀取同一個資源,但不允許其他客戶修改

**排他鎖(X鎖,寫鎖):**允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。是排他的,會阻塞其他的寫鎖和讀鎖

另外,爲了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖

  • 意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。
  • 意向排他鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖

MVCC

參考博客:一文理解Mysql MVCC

MVCC的產生原因

數據庫通常使用鎖來實現隔離性。最原生的鎖,鎖住一個資源後會禁止其他任何線程訪問同一個資源。但是很多應用的一個特點都是讀多寫少的場景,很多數據的讀取次數遠大於修改的次數,而讀取數據間互相排斥顯得不是很必要。所以就使用了一種讀寫鎖的方法,讀鎖和讀鎖之間不互斥,而寫鎖和寫鎖、讀鎖都互斥。這樣就很大提升了系統的併發能力。之後人們發現併發讀還是不夠,又提出了能不能讓讀寫之間也不衝突的方法,就是讀取數據時通過一種類似快照的方式將數據保存下來,這樣讀鎖就和寫鎖不衝突了,於是就有了MVCC.

MVCC的介紹

MVCC指的是多版本併發控制,MVCC是讀已提交和可重複讀取隔離級別的實現。

**MVCC只在 READ COMMITTED 和 REPEATABLE READ 兩個隔離級別下工作。**其他兩個隔離級別夠和MVCC不兼容, 因爲 READ UNCOMMITTED 總是讀取最新的數據行, 而不是符合當前事務版本的數據行。而 SERIALIZABLE 則會對所有讀取的行都加鎖。

InnoDB中通過undo log實現了數據的多版本,而併發控制通過鎖來實現。

關於redo log,undo log,bin log//具體以後分析

bin log:是mysql服務層產生的日誌,常用來進行數據恢復、數據庫複製,常見的mysql主從架構,就是採用slave同步master的binlog實現的, 另外通過解析binlog能夠實現mysql到其他數據源(如ElasticSearch)的數據複製。

**redo log:**記錄了數據操作在物理層面的修改,mysql中使用了大量緩存,緩存存在於內存中,修改操作時會直接修改內存,而不是立刻修改磁盤,當內存和磁盤的數據不一致時,稱內存中的數據爲髒頁(dirty page)。爲了保證數據的安全性,事務進行中時會不斷的產生redo log,在事務提交時進行一次flush操作,保存到磁盤中, redo log是按照順序寫入的,磁盤的順序讀寫的速度遠大於隨機讀寫。當數據庫或主機失效重啓時,會根據redo log進行數據的恢復,如果redo log中有事務提交,則進行事務提交修改數據。這樣實現了事務的原子性、一致性和持久性。

undo log: undo log用於數據的撤回操作,它記錄了修改的反向操作,比如,插入對應刪除,修改對應修改爲原來的數據,通過undo log可以實現事務回滾,並且可以根據undo log回溯到某個特定的版本的數據,實現MVCC。

RC和RR在使用MVCC的區別

RCRR 兩種隔離級別的事務在執行普通的讀操作時,通過訪問版本鏈的方法,使得事務間的讀寫操作得以併發執行,從而提升系統性能。RCRR 這兩個隔離級別的一個很大不同就是生成 ReadView 的時間點不同,RC 在每一次 SELECT 語句前都會生成一個 ReadView,事務期間會更新,因此在其他事務提交前後所得到的 m_ids 列表可能發生變化,使得先前不可見的版本後續又突然可見了。而 RR 只在事務的第一個 SELECT 語句時生成一個 ReadView,事務操作期間不更新。

使用流程

這裏需要用到前面的知識,關於[行格式的](# 記錄的真實數據)

這裏面有3個mysql添加的字段row_idtransaction_idroll_pointer,實現MVCC的多版本控制就主要依賴於後面2個參數。

transaction_id:用來標識最近一次對本行記錄做修改(insert|update)的事務的標識符, 即最後一次修改(insert|update)本行記錄的事務id。

roll_pointer:指寫入回滾段(rollback segment)的 undo log record (撤銷日誌記錄記錄),如果一行記錄被更新, 則 undo log record 包含 ‘重建該行記錄被更新之前內容’ 所必須的信息。也就是通過這個可以形成一條版本鏈。

image_1d6vfrv111j4guetptcts1qgp40.png-57.1kB

例子可以參考這裏,下面說一下增刪查改的不同。

SELECT

讀取創建版本小於或等於當前事務版本號,並且刪除版本爲空或大於當前事務版本號的記錄。也就是在這之前創建且沒有被刪除的記錄。

INSERT

將當前事務的版本號保存至行的創建版本號,新插入一行。

UPDATE

新插入一行,並以當前事務的版本號作爲新行的創建版本號,同時將原記錄行的刪除版本號設置爲當前事務版本號

DELETE

將當前事務的版本號保存至行的刪除版本號

樂觀鎖和悲觀鎖

基本信息可以參考我的這篇博客事務隔離性以及樂觀鎖和悲觀鎖

悲觀鎖是數據庫層面加鎖,都會阻塞去等待鎖

樂觀鎖不是數據庫層面上的鎖,是需要自己手動去加的鎖。

所以,針對樂觀鎖,一般有以下兩種是實現形式

  1. 版本號機制

  2. CAS算法

版本號機制:一般是在數據表中加上一個數據版本號version字段,表示數據被修改的次數,當數據被修改時,version值會加一。當線程A要更新數據值時,在讀取數據的同時也會讀取version值,在提交更新時,若剛纔讀取到的version值爲當前數據庫中的version值相等時才更新,否則重試更新操作,直到更新成功。

CAS算法:即compare and swap(比較與交換)

CAS算法涉及到三個操作數

  • 需要讀寫的內存值 V
  • 進行比較的值 A
  • 擬寫入的新值 U

CAS具體執行時,當且僅當預期值A符合內存地址V中存儲的值時,就用新值U替換掉舊值,並寫入到內存地址V中。否則不做更新。

用下面的圖可以更好的理解這個算法

CAS算法圖解

CAS算法帶來的問題——ABA問題

如果內存地址V初次讀取的值是A,並且在準備賦值的時候檢查到它的值仍然爲A,那我們就能說它的值沒有被其他線程改變過了嗎?如果在這段期間它的值曾經被改成了B,後來又被改回爲A,那CAS操作就會誤認爲它從來沒有被改變過。這個漏洞稱爲CAS操作的“ABA”問題。

解決方法:使用版本號來區分有沒有被修改

從Java1.5開始JDK的atomic包裏提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。

同時CAS算法也有循環開銷時間大的問題,如果長時間不成功,會給CPU帶來非常大的執行開銷

間隙鎖GAP(Next-Key鎖)

當我們用範圍條件檢索數據而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合範圍條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”。InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖。

值得注意的是:間隙鎖只會在Repeatable read隔離級別下使用~

例子:假如emp表中只有101條記錄,其empid的值分別是1,2,…,100,101

Select * from  emp where empid > 100 for update;

上面是一個範圍查詢,InnoDB不僅會對符合條件的empid值爲101的記錄加鎖,也會對empid大於101(這些記錄並不存在)的“間隙”加鎖

InnoDB使用間隙鎖的目的有兩個:

  • 爲了防止幻讀(上面也說了,Repeatable read隔離級別下再通過GAP鎖即可避免了幻讀)
  • 滿足恢復和複製的需要
    • MySQL的恢復機制要求:在一個事務未提交前,其他併發事務不能插入滿足其鎖定條件的任何記錄,也就是不允許出現幻讀

所以在RR隔離級別下使用間隙鎖可以解決幻讀問題。

死鎖

死鎖概念:死鎖是指兩個或多個事務在同一資源上互相佔用,並請求加鎖時,而導致的惡性循環現象。當多個事務以不同順序試圖加鎖同一資源時,就會產生死鎖。

一些預防措施

  1. 固定的順序訪問表和行。比如對兩個job批量更新的情形,簡單方法是對id列表先排序,後執行,這樣就避免了交叉等待鎖的情形;將兩個事務的sql順序調整爲一致,也能避免死鎖。

  2. 大事務拆小。大事務更傾向於死鎖,如果業務允許,將大事務拆小。

  3. 在同一個事務中,儘可能做到一次鎖定所需要的所有資源,減少死鎖概率。

  4. 降低隔離級別。如果業務允許,將隔離級別調低也是較好的選擇,比如將隔離級別從RR調整爲RC,可以避免掉很多因爲gap鎖造成的死鎖。

  5. 爲表添加合理的索引。可以看到如果不走索引將會爲表的每一行記錄添加上鎖,死鎖的概率大大增大。

解決死鎖方法

1.等待事務超時,主動回滾。

2.進行死鎖檢查,主動回滾某條事務,讓別的事務能繼續走下去。

千萬級別的數據的優化

MySQL 對於千萬級的大表要怎麼優化?

關於count函數

比較count(1)和count(*)

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

Mysql官方的解釋count(1)和count(*)是完全一樣的,不存在性能上的差異,但是這兩者相比較而言,更推薦使用count(*),因爲count(*)是SQL92定義的標準統計行數的語法。

關於count(字段)

他的查詢比較簡單粗暴,就是進行全表掃描,然後判斷指定字段的值是不是爲NULL,不爲NULL則累加。

相比COUNT(*)COUNT(字段)多了一個步驟就是判斷所查詢的字段是否爲NULL,所以他的性能要比COUNT(*)慢。

mysql對count(*)的優化

前面提到了COUNT(*)是SQL92定義的標準統計行數的語法,所以MySQL數據庫對他進行過很多優化。那麼,具體都做過哪些事情呢?

這裏的介紹要區分不同的執行引擎。MySQL中比較常用的執行引擎就是InnoDB和MyISAM。

MyISAM和InnoDB有很多區別,其中有一個關鍵的區別和我們接下來要介紹的COUNT(*)有關,那就是MyISAM不支持事務,MyISAM中的鎖是表級鎖;而InnoDB支持事務,並且支持行級鎖。

因爲MyISAM的鎖是表級鎖,所以同一張表上面的操作需要串行進行,所以,MyISAM做了一個簡單的優化,那就是它可以把表的總行數單獨記錄下來,如果從一張表中使用COUNT(*)進行查詢的時候,可以直接返回這個記錄下來的數值就可以了,當然,前提是不能有where條件。

MyISAM之所以可以把表中的總行數記錄下來供COUNT(*)查詢使用,那是因爲MyISAM數據庫是表級鎖,不會有併發的數據庫行數修改,所以查詢得到的行數是準確的。

但是,對於InnoDB來說,就不能做這種緩存操作了,因爲InnoDB支持事務,其中大部分操作都是行級鎖,所以可能表的行數可能會被併發修改,那麼緩存記錄下來的總行數就不準確了。

但是,InnoDB還是針對COUNT(*)語句做了些優化的。

在InnoDB中,使用COUNT(*)查詢行數的時候,不可避免的要進行掃表了,那麼,就可以在掃表過程中下功夫來優化效率了。

從MySQL 8.0.13開始,針對InnoDB的SELECT COUNT(*) FROM tbl_name語句,確實在掃表的過程中做了一些優化。前提是查詢語句中不包含WHERE或GROUP BY等條件。

我們知道,COUNT(*)的目的只是爲了統計總行數,所以,他根本不關心自己查到的具體值,所以,他如果能夠在掃表的過程中,選擇一個成本較低的索引進行的話,那就可以大大節省時間。

我們知道,InnoDB中索引分爲聚簇索引(主鍵索引)和非聚簇索引(非主鍵索引),聚簇索引的葉子節點中保存的是整行記錄,而非聚簇索引的葉子節點中保存的是該行記錄的主鍵的值。

所以,相比之下,非聚簇索引要比聚簇索引小很多,所以MySQL會優先選擇最小的非聚簇索引來掃表。所以,當我們建表的時候,除了主鍵索引以外,創建一個非主鍵索引還是有必要的。

至此,我們介紹完了MySQL數據庫對於COUNT(*)的優化,這些優化的前提都是查詢語句中不包含WHERE以及GROUP BY條件。

參考內容:不就是SELECT COUNT語句嗎,竟然能被面試官虐的體無完膚

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