1. 程式人生 > 實用技巧 >mysql 查詢隨機資料

mysql 查詢隨機資料

iwehdio的部落格園:https://www.cnblogs.com/iwehdio/

Java3y索引和鎖

MySQL筆記:22篇

JavaGuide資料索引2

MySQL技術內幕-InnoDB儲存引擎

1、索引的結構

  • 索引的優點:

    • 可以大大加快 資料的檢索速度(大大減少的檢索的資料量), 這也是建立索引的最主要的原因。
    • 通過建立唯一性索引,可以保證資料庫表中每一行資料的唯一性。
  • 索引的缺點:

    • 建立索引和維護索引需要耗費許多時間:當對錶中的資料進行增刪改的時候,如果資料有索引,那麼索引也需要動態的修改,會降低SQL執行效率。
    • 佔用物理儲存空間 :索引需要使用物理檔案儲存,也會耗費一定空間。
  • MySQL的基本儲存結構是頁,記錄都存在頁裡面:

    • mysql中頁是innodb中儲存資料的基本單位,也是mysql中管理資料的最⼩單位,和磁碟互動的時候都是以頁來進⾏的,預設是16kb,mysql中採⽤b+樹儲存資料,頁相當於b+樹中的⼀個節點。

    • 每個資料頁中都可以包含多條資料。同時,在頁的Page Header中,還包含有指向上一個和下一個資料頁的指標。這樣,各個資料頁就組成了一個雙向連結串列。

    • 而每個資料頁中的記錄又可以組成一個單向連結串列:

      • 每個資料頁都會為儲存在它裡邊的記錄生成一個頁目錄,在通過主鍵查詢某條記錄的時候可以在頁目錄中使用二分法快速定位到對應的槽,然後再遍歷該槽對應分組中的記錄即可快速找到指定的記錄。
      • 以其他列(非主鍵)作為搜尋條件:只能從最小記錄開始依次遍歷單鏈表中的每條記錄。
    • 如果我們寫 select*fromuserwhereusername='xxx'這樣沒有進行任何優化的sql語句,預設會這樣做:

      • 定位到記錄所在的頁,需要遍歷雙向連結串列,找到所在的頁。
      • 從所在的頁內中查詢相應的記錄,由於不是根據主鍵查詢,只能遍歷所在頁的單鏈表了。
  • B樹:

    • 每個節點最多有m個孩子,m稱為b樹的階。
    • 除了根節點和葉子節點外,其它每個節點至少有。Ceil(m/2)個孩子。
    • 若根節點不是葉子節點,則至少有2個孩子。
    • 所有葉子節點都在同一層,且不包含鍵值資訊。
    • 每個非終端節點包含n個鍵值資訊。
    • 關鍵字的個數n滿足: ceil(m/2)-1 <=n <= m-1ki(i=1,....)為關鍵字,且關鍵字升序排序。
    • Pi(i=1..n)為指向子樹根節點的指標。P(i-1)指向的子樹的所有節點關鍵字均小於ki,但都大於k(i-1)。

    • 總而言之,B樹的葉子節點中只包含資料項和資料項對應的鍵值。
    • 而非葉子節點,首先包含了用於分割範圍的鍵值(升序)和該鍵值對應的資料項(內部資料)。在n個鍵值分割出的n+1個範圍中,這個節點用n+1個指標指向該結點的n+1個孩子節點。
    • B樹定位某個值很快且只需要少量IO操作,但是如果要進行範圍檢索則需要大量IO操作。
  • B+樹:

    • 每個結點至多有m個子女。
    • 除根結點外,每個結點至少有[m/2]個子女,根結點至少有兩個子女。
    • 有k個子女的結點必有k個鍵值。
    • 父節點中持有訪問子節點的指標。
    • 父節點的鍵值在子節點中都存在,而且要麼是最小值,要麼是最大值。如果節點中鍵值是升序的方式,父節點的鍵值是子節點的最小值。
    • 最底層的節點是葉子節點。
    • 除葉子節點之外,其他節點不儲存資料,只儲存鍵值和指標。
    • 葉⼦節點包含了所有資料的鍵值以及資料,葉⼦節點之間⽤連結串列連線起來,可以⾮常⽅便的⽀持範圍查詢。

  • b+樹與b-樹的幾點不同:

    1. b+樹中一個節點如果有k個鍵值,最多可以包含k個子節點(k個鍵值對應k個指標)﹔而b-樹對應k+1個子節點(多了一個指向子節點的指標)。
    2. b+樹除葉子節點之外其他節點只儲存鍵值和指向子節點的指標,而b-樹還儲存了資料,這樣同樣大小情況下,b+樹可以儲存更多的鍵值。
    3. b+樹葉子節點中儲存了所有鍵值及資料,並且多個節點用連結串列連線,從上圖中看子節點中資料從左向右是有序的,這樣快速可以支撐範圍查詢(先定位範圍的最大值和最小值,然後子節點中依靠連結串列遍歷範圍資料)。
  • B-Tree和B+Tree的優劣比較:

    1. B-Tree因為非葉子結點也儲存具體資料,所以在查詢某個關鍵字的時候找到即可返回。而B+Tree所有的資料都在葉子結點,每次查詢都得到葉子結點。所以在同樣高度的B-Tree和B+Tree中, B-Tree查詢某個關鍵字的效率更高。
    2. 由於B+Tree所有的資料都在葉子結點,並且結點之間有指標連線,在找大於某個關鍵字或者小於某個關鍵字的資料的時候,B+Tree只需要找到該關鍵字然後沿著連結串列遍歷就可以了,而B-Tree還需要遍歷該關鍵字結點的根結點去搜索。
    3. 由於B-Tree的每個結點(這裡的結點可以理解為一個數據頁)都儲存主鍵+實際資料,而B+Tree非葉子結點只儲存關鍵字資訊,而每個頁的大小有限是有限的,所以同一頁能儲存的B-Tree的資料會比B+Tree儲存的更少。這樣同樣總量的資料,B-Tree的深度會更大,增大查詢時的磁碟I/О次數,進而影響查詢效率。
  • 索引影響資料增刪改效率:

    • B+樹是一顆平衡樹,如果我們對這顆樹增刪改的話,那肯定會破壞它的原有結構。
    • 要維持平衡樹,就必須做額外的工作。正因為這些額外的工作開銷,導致索引會降低增刪改的速度。
  • 聯合索引的B+樹結構:

    • 聯合索引就是指,對錶上的多個列進行索引。

    • 本質上來說,聯合索引也是一顆B+樹,只不過鍵值的數量不是1,而是大於等於2。例如對於索引(a,b),鍵值可能為(2,4)。

    • 與之前一樣,鍵值也是排序的。不過是先按第一個鍵值排序,再按第二個鍵值排序。

    • 在這種情況下,顯然對於a列或(a,b)列進行符合要求的查詢是可以使用索引的。但是單獨對b列的查詢不可以,因為b列的值只在a值相同時是有序的,並不是全域性有序的。

  • 雜湊索引:

    • 雜湊索引就是採用一定的雜湊演算法,把鍵值換算成新的雜湊值,檢索時不需要類似B+樹那樣從根節點到葉子節點逐級查詢,只需一次雜湊演算法即可立刻定位到相應的位置,速度非常快。

    • 本質上就是把鍵值換算成新的雜湊值,根據這個雜湊值來定位。

    • 雜湊索引的侷限:

      • 雜湊索引沒辦法利用索引完成排序。
      • 不支援最左匹配原則。
      • 在有大量雜湊值情況下,雜湊索引的效率也是極低的---->雜湊碰撞問題。
      • 不支援範圍查詢。

2、索引的分類

  • 索引的型別:

    • 主鍵索引:資料表的主鍵列使用的就是主鍵索引。

      • 一張資料表有隻能有一個主鍵,並且主鍵不能為null,不能重複。
      • 在mysql的InnoDB的表中,當沒有顯式的指定表的主鍵時,InnoDB會自動先檢查表中是否有非空的唯一索引的欄位,如果有,則選擇該欄位為預設的主鍵,否則InnoDB將會自動建立一個6Byte的自增主鍵。
    • 二級索引(輔助索引):

      • 二級索引的葉子節點儲存的資料是主鍵。也就是說,通過二級索引,可以定位主鍵的位置。
      • 唯一索引,普通索引,字首索引等索引屬於二級索引。
      • 唯一索引(Unique Key) :唯一索引也是一種約束。唯一索引的屬性列不能出現重複的資料,但是允許資料為NULL,一張表允許建立多個唯一索引。建立唯一索引的目的大部分時候都是為了該屬性列的資料的唯一性,而不是為了查詢效率。
      • 普通索引(Index) :普通索引的唯一作用就是為了快速查詢資料,一張表允許建立多個普通索引,並允許資料重複和NULL。
      • 字首索引(Prefix) :字首索引只適用於字串型別的資料。字首索引是對文字的前幾個字元建立索引,相比普通索引建立的資料更小, 因為只取前幾個字元。
      • 全文索引(Full Text) :全文索引主要是為了檢索大文字資料中的關鍵字的資訊,是目前搜尋引擎資料庫使用的一種技術。Mysql5.6之前只有MYISAM引擎支援全文索引,5.6之後InnoDB也支援了全文索引。
  • 聚集索引和非聚焦索引:

    • 聚集索引就是以主鍵建立的索引。
      • 聚集指的是索引結構與資料一起存放。
      • 優點:查詢速度很快。
      • 缺點:依賴於有序的資料;更新的代價大(所以主鍵一般不更新)。
    • 非聚集索引就是以非主鍵建立的索引。
      • 非聚集指的是索引結構與資料分開存放。
      • 優點:更新代價比聚集索引低。
      • 缺點:也依賴於有序的資料;可能需要回表二次查詢。
    • 非聚集索引在建立的時候未必是單列的,可以多個列來建立索引。
  • 區別:

    • 聚集索引在葉子節點儲存的是表中的資料。
    • 非聚集索引在葉子節點儲存的是主鍵和索引列。
    • 使用非聚集索引查詢出資料時,拿到葉子上的主鍵再去查到想要查詢的資料。(拿到主鍵再查詢這個過程叫做回表)。
  • 理解覆蓋索引:

    • 如果不是聚集索引,葉子節點儲存的是主鍵+索引列值。最終還是要“回表”,也就是要通過主鍵再查詢一次。這樣就會比較慢。
    • 但是如果使用的是覆蓋索引,就是我們要的資料就是索引列(或者索引列包含查詢列),就不用回表了。

3、MySQL中的鎖

  • 一般情況下,鎖是資料庫隱式加的:

    • 對於UPDATE 、DELETE 、INSERT語句,InnoDB會自動給涉及資料集加排他鎖(X)。
    • MylSAM在執行查詢語句SELECT前,會自動給涉及的所有表加讀鎖,在執行更新操作(UPDATE、DELETE、工INSERT等)前,會自動給涉及的表加寫鎖,這個過程並不需要使用者干預。
  • 從鎖的粒度來看:

    • 表鎖:開銷小,加鎖快;不會出現死鎖;鎖定力度大,發生鎖衝突概率高,併發度最低。
    • 行鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度小,發生鎖衝突的概率低,併發度高。
    • InnoDB行鎖和表鎖都支援;MylSAM只支援表鎖。
    • InnoDB只有通過索引條件檢索資料才使用行級鎖,否則,InnoDB將使用表鎖。
  • 表鎖:

    • 分為表讀鎖和表寫鎖。
    • 讀讀不阻塞,讀寫阻塞,寫寫阻塞。
    • 如果某個程序想要獲取讀鎖,同時另外一個程序想要獲取寫鎖。在mysql裡邊,寫鎖是優先於讀鎖的。
    • 寫鎖和讀鎖優先順序的問題是可以通過引數調節的: max_write_lock_countlow-priority-updates
    • MylSAM可以支援查詢和插入操作的併發進行。可以通過系統變數concurrent_insert來指定是否啟用。
    • 在MyISAM中它預設是:如果MylSAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM允許在一個程序讀表的同時,另一個程序從表尾插入記錄。但是InnoDB儲存引擎是不支援的。
  • 行鎖:

    • 行鎖也分為讀鎖(共享鎖)和寫鎖(排它鎖),只不過鎖的對應是一行資料。
    • 為了允許行鎖和表鎖共存,實現多粒度鎖機制, InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖︰
      • 意向共享鎖(IS)︰事務打算給資料行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表
        的IS鎖。
      • 意向排他鎖(IX)︰事務打算給資料行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。
      • 意向鎖也是資料庫隱式幫我們做了,不需要程式設計師操心。
  • 鎖與事務隔離級別:

    • 資料庫事務有不同的隔離級別,不同的隔離級別對鎖的使用是不同的,鎖的應用最終導致不同事務的隔離級別

      • 事務的隔離級別就是通過鎖的機制來實現,只不過隱藏了加鎖細節
    • MVCC(Multi-Version Concurrency Control)多版本併發控制,可以簡單地認為:MVCC就是行級鎖的一個變種(升級版)

    • 表鎖中我們讀寫是阻塞的,基於提升併發效能的考慮,MVCC一般讀寫是不阻塞的(所以說MVCC很多情況下避免了加鎖的操作)。

      • MVCC實現的讀寫不阻塞正如其名:多版本併發控制--->通過一定機制生成一個數據請求時間點的一致性資料快照(Snapshot),並用這個快照來提供一定級別(語句級或事務級)的一致性讀取
  • 從使用者的角度來看,好像是資料庫可以提供同一資料的多個版本

  • 快照有兩個級別

    • 語句級

      • 針對於 Readcommitted隔離級別。
    • 事務級別

      • 針對於 Repeatableread隔離級別。
    • 語句級指的是每個修改語句都會生成新快照;事務級指的是每個事務中都使用的是事務開始時建立的快照。

  • 事務隔離級別的鎖實現:

    • Read uncommitted會出現的現象--->髒讀:一個事務讀取到另外一個事務未提交的資料
      • 出現髒讀的原因是因為在讀的時候沒有加讀鎖,導致可以讀取出還沒釋放鎖的記錄
      • 髒讀過程:
        • 事務A讀取記錄(沒有加任何的鎖)。
        • 事務B修改記錄(此時加了寫鎖,並且還沒有commit-->也就沒有釋放掉寫鎖)。
        • 事務A再次讀取記錄(此時因為事務A在讀取時沒有加任何鎖,所以可以讀取到事務B還沒提交的(沒釋放掉寫鎖)的記錄。
    • Read committed避免髒讀的做法:
      • 在讀取的時候生成一個版本號,直到其他事務commit,資料被修改了之後,才會有新的版本號。
      • 可重複讀過程:
        • 事務A讀取了記錄(生成版本號)。
        • 事務B修改了記錄(此時加了寫鎖)。
        • 事務A再讀取的時候,是依據最新的版本號來讀取的(當事務B執行commit了之後,會生成一個新的版本號),如果事務B還沒有commit,那事務A讀取的還是之前版本號的資料。
    • Read committed出現的現象--->不可重複讀:一個事務讀取到另外一個事務已經提交的資料,也就是說一個事務可以看到其他事務所做的修改。
      • Read committed語句級別的快照!每次讀取的都是當前最新的版本
      • Repeatable read避免不可重複讀是事務級別的快照。每次讀取的都是當前事務的版本,即使資料被其他事務修改了,也只會讀取當前事務版本的資料。
      • InnoDB的MVCC,是通過在每行記錄後面儲存兩個隱藏的列來實現的。
        • 這兩個列,一個儲存了行的建立時間,一個儲存行的過期時間(或刪除時間)。當然儲存的並不是實際的時間值,而是系統版本號(system version number) 。
        • 每開始一個新的事務,系統版本號都會自動遞增。事務開始時刻的系統版本號會作為事務的版本號。用來和查詢到的每行記錄的版本號進往比較。
      • InnoDB會根據以下兩個條件檢查每行記錄:
        • 只查詢版本早於當前事務版本的資料行(也就是,行的系統版本號小於或等於事務的系統版本號),這樣可以確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或者修改過的。
        • 行的刪除版本要麼未定義,要麼大於當前事務版本號。這可以確保事務讀取到的行,在事務開始之前未被刪除。
    • Repeatable read出現的現象--->幻讀:是指在一個事務內讀取到了別的事務插入的資料,導致前後讀取不一致。
      • MySQL的 Repeatableread隔離級別加上GAP間隙鎖已經處理了幻讀了
  • 樂觀鎖和悲觀鎖:

    • 丟失更新Repeatable read隔離級別下,一個事務的更新覆蓋了其它事務的更新結果
    • 解決的方法:
      • 使用Serializable隔離級別,事務是序列執行的。
      • 樂觀鎖。
      • 悲觀鎖。
    • 悲觀鎖:
      • 悲觀在認為資料衝突是大概率會發生的。
      • 手動加行排它鎖select * from xxxx for update
    • 樂觀鎖:
      • 樂觀鎖是一種思想,具體實現是,表中有一個版本欄位,第一次讀的時候,獲取到這個欄位。處理完業務邏輯開始更新的時候,需要再次檢視該欄位的值是否和第一次的一樣。如果一樣則更新,反之拒絕。
      • 之所以叫樂觀,因為這個模式沒有從資料庫加鎖,等到更新的時候再判斷是否可以更新。
      • 樂觀在認為資料衝突是小概率發生的。
  • 間隙鎖:

    • 間隙鎖只會在 Repeatable read隔離級別下使用。
    • 當我們用範圍條件檢索資料而不是相等條件檢索資料,並請求共享或排他鎖時,InnoDB會給符合範圍條件的已有資料記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”。InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖。
    • InnoDB使用間隙鎖的目的有兩個:
      • 為了防止幻讀( Repeatableread隔離級別下再通過GAP鎖即可避免了幻讀)。
      • 滿足恢復和複製的需要。MySQL的恢復機制要求:在一個事務未提交前,其他併發事務不能插入滿足其鎖定條件的任何記錄,也就是不允許出現幻讀

iwehdio的部落格園:https://www.cnblogs.com/iwehdio/