【面試】網易遊戲面試題目整理及答案(3)

數據庫部分

MySQL 事務

  1. 事務的隔離級別有哪些?MySQL的默認隔離級別是什麼?什麼是幻讀,髒讀,不可重複讀呢?MySQL事務的四大特性以及實現原理。MVCC熟悉嗎,它的底層原理?
    答:MySQL 事務主要用於處理操作量大,複雜度高的數據事務是由一組SQL語句組成的邏輯處理單元,具有4個屬性,通常簡稱爲事務的ACID屬性
  • A (Atomicity) 原子性:整個事務中的所有操作,要麼全部完成,要麼全部不完成,不可能停滯在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣
  • C (Consistency) 一致性:在事務開始之前和事務結束以後,數據庫的完整性約束沒有被破壞
  • I (Isolation)隔離性:一個事務的執行不能其它事務干擾。即一個事務內部的操作及使用的數據對其它併發事務是隔離的,併發執行的各個事務之間不能互相干擾
  • D (Durability) 持久性:在事務完成以後,該事務所對數據庫所作的更改便持久的保存在數據庫之中,並不會被回滾

併發事務處理帶來的問題

  • 更新丟失(Lost Update):事務A和事務B選擇同一行,然後基於最初選定的值更新該行時,由於兩個事務都不知道彼此的存在,就會發生丟失更新問題
  • 髒讀(Dirty Reads):事務A讀取了事務B更新的數據,然後B回滾操作,那麼A讀取到的數據是髒數據
  • 不可重複讀(Non-Repeatable Reads):事務 A 多次讀取同一數據,事務B在事務A多次讀取的過程中,對數據作了更新並提交,導致事務A- 多次讀取同一數據時,結果不一致。
  • 幻讀(Phantom Reads):幻讀與不可重複讀類似。它發生在一個事務A讀取了幾行數據,接着另一個併發事務B插入了一些數據時。在隨後的查詢中,事務A就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱爲幻讀。

幻讀和不可重複讀的區別:

  • 不可重複讀的重點是修改:在同一事務中,同樣的條件,第一次讀的數據和第二次讀的數據不一樣。(因爲中間有其他事務提交了修改)
  • 幻讀的重點在於新增或者刪除:在同一事務中,同樣的條件,,第一次和第二次讀出來的記錄數不一樣。(因爲中間有其他事務提交了插入/刪除)

併發事務處理帶來的問題的解決辦法:

  • “更新丟失”通常是應該完全避免的。但防止更新丟失,並不能單靠數據庫事務控制器來解決,需要應用程序對要更新的數據加必要的鎖來解決,因此,防止更新丟失應該是應用的責任。
  • “髒讀” 、 “不可重複讀”和“幻讀” ,其實都是數據庫讀一致性問題,必須由數據庫提供一定的事務隔離機制來解決
    ①一種是加鎖:在讀取數據前,對其加鎖,阻止其他事務對數據進行修改。
    ②另一種是數據多版本併發控制(MultiVersion Concurrency Control,簡稱 MVCC 或 MCC),也稱爲多版本數據庫:不用加任何鎖, 通過一定機制生成一個數據請求時間點的一致性數據快照 (Snapshot), 並用這個快照來提供一定級別 (語句級或事務級) 的一致性讀取。從用戶的角度來看,好象是數據庫可以提供同一數據的多個版本。
  1. 事務隔離級別
    答:數據庫事務的隔離級別有4種,由低到高分別爲
  • READ-UNCOMMITTED(讀未提交)最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複讀
  • READ-COMMITTED(讀已提交): 允許讀取併發事務已經提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
  • REPEATABLE-READ(可重複讀): 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生
  • SERIALIZABLE(可串行化)最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀
    查看當前數據庫的事務隔離級別:
show variables like 'tx_isolation'

數據庫的事務隔離越嚴格,併發副作用越小,但付出的代價就越大,因爲事務隔離實質上就是使事務在一定程度上“串行化”進行,這顯然與“併發”是矛盾的。同時,不同的應用對讀一致性和事務隔離程度的要求也是不同的,比如許多應用對“不可重複讀”和“幻讀”並不敏感,可能更關心數據併發訪問的能力。
Read uncommitted:讀未提交,就是一個事務可以讀取另一個未提交事務的數據
Read committed:讀提交,顧名思義,就是一個事務要等另一個事務提交後才能讀取數據。解決髒讀問題。
Repeatable read:重複讀,就是在開始讀取數據(事務開啓)時,不再允許修改操作。MySQL的默認事務隔離級別。解決可能的不可重複讀問題。不可重複讀對應的是修改,即UPDATE操作。但是可能還會有幻讀問題。因爲幻讀問題對應的是插入INSERT操作,而不是UPDATE操作
Serializable 序列化:Serializable 是最高的事務隔離級別,在該級別下,事務串行化順序執行,可以避免髒讀、不可重複讀與幻讀。簡單來說,Serializable會在讀取的每一行數據上都加鎖,所以可能導致大量的超時和鎖爭用問題。這種事務隔離級別效率低下,比較耗數據庫性能,一般不使用。
事務隔離級別的比較
需要說明的是,事務隔離級別和數據訪問的併發性是對立的,事務隔離級別越高併發性就越差。所以要根據具體的應用來確定合適的事務隔離級別,這個地方沒有萬能的原則。

MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;命令來查看,MySQL 8.0 該命令改爲SELECT @@transaction_isolation;
這裏需要注意的是:與 SQL 標準不同的地方在於InnoDB 存儲引擎在 REPEATABLE-READ(可重讀)事務隔離級別下使用的是Next-Key Lock 算法,因此可以避免幻讀的產生,這與其他數據庫系統(如 SQL Server)是不同的。所以說InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)已經可以完全保證事務的隔離性要求,即達到了 SQL標準的 SERIALIZABLE(可串行化)隔離級別,而且保留了比較好的併發性能。

因爲隔離級別越低,事務請求的鎖越少,所以大部分數據庫系統的隔離級別都是READ-COMMITTED(讀已提交):,但是你要知道的是InnoDB 存儲引擎默認使用 REPEATABLE-READ(可重讀)並不會有任何性能損失。

  1. MVCC 多版本併發控制
    答:MySQL的大多數事務型存儲引擎實現都不是簡單的行級鎖。基於提升併發性考慮,一般都同時實現了多版本併發控制(MVCC),包括Oracle、PostgreSQL。只是實現機制各不相同。可以認爲 **MVCC 是行級鎖的一個變種,但它在很多情況下避免了加鎖操作,因此開銷更低。**雖然實現機制有所不同,但大都實現了非阻塞的讀操作,寫操作也只是鎖定必要的行。**MVCC 的實現是通過保存數據在某個時間點的快照來實現的。**也就是說不管需要執行多長時間,每個事物看到的數據都是一致的。典型的MVCC實現方式,分爲樂觀(optimistic)併發控制和悲觀(pressimistic)併發控制。下邊通過 InnoDB的簡化版行爲來說明 MVCC 是如何工作的。
    InnoDB 的 MVCC,是通過在每行記錄後面保存兩個隱藏的列來實現。這兩個列,一個保存了行的創建時間,一個保存行的過期時間(刪除時間)。當然存儲的並不是真實的時間,而是系統版本號(system version number)每開始一個新的事務,系統版本號都會自動遞增。事務開始時刻的系統版本號會作爲事務的版本號,用來和查詢到的每行記錄的版本號進行比較

REPEATABLE READ(可重讀)隔離級別下MVCC如何工作:

  • SELECT
    InnoDB會根據以下兩個條件檢查每行記錄:
    1.InnoDB只查找版本早於當前事務版本的數據行,這樣可以確保事務讀取的行,要麼是在開始事務之前已經存在要麼是事務自身插入或者修改過的
    2.行的刪除版本號要麼未定義,要麼大於當前事務版本號,這樣可以確保事務讀取到的行在事務開始之前未被刪除
    只有符合上述兩個條件的纔會被查詢出來
  • INSERT:InnoDB爲新插入的每一行保存當前系統版本號作爲行版本號
  • DELETE:InnoDB爲刪除的每一行保存當前系統版本號作爲行刪除標識
  • UPDATE:InnoDB爲插入的一行新紀錄保存當前系統版本號作爲行版本號,同時保存當前系統版本號到原來的行作爲刪除標識
    保存這兩個額外系統版本號,使大多數操作都不用加鎖。使數據操作簡單,性能很好,並且也能保證只會讀取到符合要求的行。不足之處是每行記錄都需要額外的存儲空間,需要做更多的行檢查工作和一些額外的維護工作。

MVCC 只在 COMMITTED READ(讀提交)和REPEATABLE READ(可重複讀)兩種隔離級別下工作

  1. 事務日誌
    InnoDB 使用日誌來減少提交事務時的開銷。因爲日誌中已經記錄了事務,就無須在每個事務提交時把緩衝池的髒塊刷新(flush)到磁盤中。事務修改的數據和索引通常會映射到表空間的隨機位置,所以刷新這些變更到磁盤需要很多隨機 IO
    InnoDB 假設使用常規磁盤,隨機IO比順序IO昂貴得多,因爲一個IO請求需要時間把磁頭移到正確的位置,然後等待磁盤上讀出需要的部分,再轉到開始位置。
    InnoDB 用日誌把隨機IO變成順序IO。一旦日誌安全寫到磁盤,事務就持久化了,即使斷電了,InnoDB可以重放日誌並且恢復已經提交的事務。
    InnoDB 使用一個後臺線程智能地刷新這些變更到數據文件。這個線程可以批量組合寫入,使得數據寫入更順序,以提高效率。

事務日誌可以幫助提高事務效率:

  • 使用事務日誌,存儲引擎在修改表的數據時只需要修改其內存拷貝,再把該修改行爲記錄到持久在硬盤上的事務日誌中,而不用每次都將修改的數據本身持久到磁盤。
  • 事務日誌採用的是追加的方式,因此寫日誌的操作是磁盤上一小塊區域內的順序I/O,而不像隨機I/O需要在磁盤的多個地方移動磁頭,所以採用事務日誌的方式相對來說要快得多。
  • 事務日誌持久以後,內存中被修改的數據在後臺可以慢慢刷回到磁盤。
  • 如果數據的修改已經記錄到事務日誌並持久化,但數據本身沒有寫回到磁盤,此時系統崩潰,存儲引擎在重啓時能夠自動恢復這一部分修改的數據。

目前來說,大多數存儲引擎都是這樣實現的,我們通常稱之爲預寫式日誌(Write-Ahead Logging),修改數據需要寫兩次磁盤

  1. 事務的實現
    答:事務的實現是基於數據庫的存儲引擎。不同的存儲引擎對事務的支持程度不一樣。MySQL 中支持事務的存儲引擎有 InnoDB 和 NDB事務的實現就是如何實現ACID特性。事務的隔離性是通過鎖實現,而事務的原子性、一致性和持久性則是通過事務日誌實現

補充問題:事務是如何通過日誌來實現的?
答:事務日誌包括:重做日誌redo回滾日誌undo
1)redo log(重做日誌) 實現持久化和原子性
在innoDB的存儲引擎中,事務日誌通過重做(redo)日誌和innoDB存儲引擎的日誌緩衝(InnoDB Log Buffer)實現。事務開啓時,事務中的操作,都會先寫入存儲引擎的日誌緩衝中,在事務提交之前,這些緩衝的日誌都需要提前刷新到磁盤上持久化,這就是DBA們口中常說的“日誌先行”(Write-Ahead Logging)。當事務提交之後,在Buffer Pool中映射的數據文件纔會慢慢刷新到磁盤。此時如果數據庫崩潰或者宕機,那麼當系統重啓進行恢復時,就可以根據redo log中記錄的日誌,把數據庫恢復到崩潰前的一個狀態。未完成的事務,可以繼續提交,也可以選擇回滾,這基於恢復的策略而定。
系統啓動的時候,就已經爲redo log分配了一塊連續的存儲空間,以順序追加的方式記錄Redo Log,通過順序IO來改善性能所有的事務共享redo log的存儲空間,它們的Redo Log按語句的執行順序,依次交替的記錄在一起
2)undo log(回滾日誌) 實現一致性
undo log 主要爲事務的回滾服務。在事務執行的過程中,除了記錄redo log,還會記錄一定量的undo logundo log記錄了數據在每個操作前的狀態,如果事務執行過程中需要回滾,就可以根據undo log進行回滾操作。單個事務的回滾,只會回滾當前事務做的操作,並不會影響到其他的事務做的操作
Undo記錄的是已部分完成並且寫入硬盤的未完成的事務,默認情況下回滾日誌是記錄下表空間中的(共享表空間或者獨享表空間)

二種日誌均可以視爲一種恢復操作,redo_log是恢復提交事務修改的頁操作,而undo_log是回滾行記錄到特定版本。二者記錄的內容也不同,redo_log是物理日誌,記錄頁的物理修改操作,而undo_log是邏輯日誌,根據每行記錄進行記錄

補充問題:MySQL 有多少種日誌嗎?

  • 錯誤日誌:記錄出錯信息,也記錄一些警告信息或者正確的信息。
  • 查詢日誌:記錄所有對數據庫請求的信息,不論這些請求是否得到了正確的執行。
  • 慢查詢日誌:設置一個閾值,將運行時間超過該值的所有SQL語句都記錄到慢查詢的日誌文件中。
  • 二進制日誌:記錄對數據庫執行更改的所有操作。
  • 中繼日誌:中繼日誌也是二進制日誌,用來給slave 庫恢復
  • 事務日誌:重做日誌redo和回滾日誌undo
  1. MySQL對分佈式事務的支持
    答:分佈式事務的實現方式有很多,既可以採用 InnoDB 提供的原生的事務支持,也可以採用消息隊列來實現分佈式事務的最終一致性。MySQL 從 5.0.3 InnoDB 存儲引擎開始支持XA協議的分佈式事務。**一個分佈式事務會涉及多個行動,這些行動本身是事務性的。所有行動都必須一起成功完成,或者一起被回滾。**在MySQL中,使用分佈式事務涉及一個或多個資源管理器和一個事務管理器
    MySQL分佈式事務模型
    如圖,MySQL 的分佈式事務模型分爲三塊:應用程序(AP)、資源管理器(RM)、事務管理器(TM):
  • 應用程序:定義了事務的邊界,指定需要做哪些事務;
  • 資源管理器:提供了訪問事務的方法,通常一個數據庫就是一個資源管理器;
  • 事務管理器:協調參與了全局事務中的各個事務。

分佈式事務採用兩段式提交(two-phase commit)的方式:

  • 第一階段所有的事務節點開始準備,告訴事務管理器ready。
  • 第二階段事務管理器告訴每個節點是commit還是rollback。如果有一個節點失敗,就需要全局的節點全部rollback,以此保障事務的原子性。

MySQL鎖機制

  1. 數據庫的樂觀鎖和悲觀鎖?MySQL 中有哪幾種鎖,列舉一下?MySQL中InnoDB引擎的行鎖是怎麼實現的?MySQL 間隙鎖有沒有了解,死鎖有沒有了解,寫一段會造成死鎖的 sql 語句,死鎖發生瞭如何解決,MySQL 有沒有提供什麼機制去解決死鎖?
    答:鎖是計算機協調多個進程或線程併發訪問某一資源的機制。在數據庫中,除傳統的計算資源(如CPU、RAM、I/O等)的爭用以外,數據也是一種供許多用戶共享的資源。數據庫鎖定機制簡單來說,就是數據庫爲了保證數據的一致性,而使各種共享資源在被併發訪問變得有序所設計的一種規則。

1. 鎖的分類:
1)從對數據操作的類型分類:

  • 讀鎖(共享鎖):針對同一份數據,多個讀操作可以同時進行,不會互相影響
  • 寫鎖(排他鎖):當前寫操作沒有完成前,它會阻斷其他寫鎖和讀鎖
    2)從對數據操作的粒度分類:
    爲了儘可能提高數據庫的併發度,每次鎖定的數據範圍越小越好,理論上每次只鎖定當前操作的數據的方案會得到最大的併發度,但是管理鎖是很耗資源的事情(涉及獲取,檢查,釋放鎖等動作),因此數據庫系統需要在高併發響應和系統性能兩方面進行平衡,這樣就產生了“鎖粒度(Lock granularity)”的概念。
  • 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低(MyISAM 和 MEMORY 存儲引擎採用的是表級鎖);
  • 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高(InnoDB 存儲引擎既支持行級鎖也支持表級鎖,但默認情況下是採用行級鎖);
  • 頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。
    適用:從鎖的角度來說,表級鎖更適合於以查詢爲主,只有少量按索引條件更新數據的應用,如Web應用;而行級鎖則更適合於有大量按索引條件併發更新少量不同數據,同時又有併發查詢的應用,如一些在線事務處理(OLTP)系統。
    不同存儲引擎的加鎖區別

2. MyISAM表鎖
MyISAM 的表鎖有兩種模式:

  • 表共享讀鎖 (Table Read Lock):不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求;
  • 表獨佔寫鎖 (Table Write Lock):會阻塞其他用戶對同一表的讀和寫操作;
    MyISAM 表的讀操作與寫操作之間,以及寫操作之間是串行的。當一個線程獲得對一個表的寫鎖後, 只有持有鎖的線程可以對錶進行更新操作。其他線程的讀、 寫操作都會等待,直到鎖被釋放爲止。默認情況下,寫鎖比讀鎖具有更高的優先級:當一個鎖釋放時,這個鎖會優先給寫鎖隊列中等候的獲取鎖請求,然後再給讀鎖隊列中等候的獲取鎖請求。

3. InnoDB行鎖
InnoDB 實現了以下兩種類型的行鎖:

  • 共享鎖(S):允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。
  • 排他鎖(X):允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。
    爲了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB 還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖
  • 意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的 IS 鎖。
  • 意向排他鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的 IX 鎖。
    索引失效會導致行鎖變表鎖。比如 vchar 查詢不寫單引號的情況。
    1)加鎖機制
    樂觀鎖與悲觀鎖是兩種併發控制的思想,可用於解決丟失更新問題
    樂觀鎖會“樂觀地”假定大概率不會發生併發更新衝突,訪問、處理數據過程中不加鎖,只在更新數據時再根據版本號或時間戳判斷是否有衝突,有則處理,無則提交事務。用數據版本(Version)記錄機制實現,這是樂觀鎖最常用的一種實現方式
    悲觀鎖會“悲觀地”假定大概率會發生併發更新衝突,訪問、處理數據前就加排他鎖,在整個數據處理過程中鎖定數據,事務提交或回滾後才釋放鎖。另外與樂觀鎖相對應的,悲觀鎖是由數據庫自己實現了的,要用的時候,我們直接調用數據庫的相關語句就可以了。
    2)鎖模式(InnoDB有三種行鎖的算法)
  • 記錄鎖(Record Locks):單個行記錄上的鎖。對索引項加鎖,鎖定符合條件的行。其他事務不能修改和刪除加鎖項;
SELECT * FROM table WHERE id = 1 FOR UPDATE;

它會在 id=1 的記錄上加上記錄鎖,以阻止其他事務插入,更新,刪除 id=1 這一行
在通過 主鍵索引 與 唯一索引 對數據行進行 UPDATE 操作時,也會對該行數據加記錄鎖:

-- id 列爲主鍵列或唯一索引列
UPDATE SET age = 50 WHERE id = 1;
  • 間隙鎖(Gap Locks):當我們使用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖。對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙”。
    InnoDB 也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖。
    對索引項之間的“間隙”加鎖,鎖定記錄的範圍(對第一條記錄前的間隙或最後一條將記錄後的間隙加鎖),不包含索引項本身。其他事務不能在鎖範圍內插入數據,這樣就防止了別的事務新增幻影行。
    間隙鎖基於非唯一索引,它鎖定一段範圍內的索引記錄。間隙鎖基於下面將會提到的Next-Key Locking 算法,請務必牢記:使用間隙鎖鎖住的是一個區間,而不僅僅是這個區間中的每一條數據
SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;

即所有在(1,10)區間內的記錄行都會被鎖住,所有id 爲 2、3、4、5、6、7、8、9 的數據行的插入會被阻塞,但是 1 和 10 兩條記錄行並不會被鎖住。
GAP鎖的目的,是爲了防止同一事務的兩次當前讀,出現幻讀的情況

  • 臨鍵鎖(Next-key Locks):臨鍵鎖,是記錄鎖與間隙鎖的組合,它的封鎖範圍,既包含索引記錄,又包含索引區間。(臨鍵鎖的主要目的,也是爲了避免幻讀(Phantom Read)。如果把事務的隔離級別降級爲RC,臨鍵鎖則也會失效。)
    Next-Key 可以理解爲一種特殊的間隙鎖,也可以理解爲一種特殊的算法。通過臨建鎖可以解決幻讀的問題。每個數據行上的非唯一索引列上都會存在一把臨鍵鎖,當某個事務持有該數據行的臨鍵鎖時,會鎖住一段左開右閉區間的數據。需要強調的一點是,InnoDB 中行級鎖是基於索引實現的,臨鍵鎖只與非唯一索引列有關,在唯一索引列(包括主鍵列)上不存在臨鍵鎖。
    對於行的查詢,都是採用該方法,主要目的是解決幻讀的問題。

補充問題:select for update有什麼含義,會鎖表還是鎖行還是其他?
for update適用於InnoDB,且必須在事務塊(BEGIN/COMMIT)中才能生效。在進行事務操作時,通過“for update”語句,MySQL會對查詢結果集中每行數據都添加排他鎖,其他線程對該記錄的更新與刪除操作都會阻塞。排他鎖包含行鎖、表鎖。
InnoDB這種行鎖實現特點意味着:只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!假設有個表單 products ,裏面有id跟name二個欄位,id是主鍵。

  • 明確指定主鍵,並且有該記錄,則row lock
SELECT * FROM products WHERE id='3' FOR UPDATE;
SELECT * FROM products WHERE id='3' and type=1 FOR UPDATE;
  • 明確指定主鍵,若沒有該記錄,則無lock
SELECT * FROM products WHERE id='-1' FOR UPDATE;
  • 無主鍵, table lock
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;
  • 主鍵不明確, table lock
SELECT * FROM products WHERE id<>'3' FOR UPDATE;
  • 主鍵不明確,table lock
SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;

注1: FOR UPDATE僅適用於InnoDB,且必須在交易區塊(BEGIN/COMMIT)中才能生效。注2: 要測試鎖定的狀況,可以利用MySQL的Command Mode ,開二個視窗來做測試。

補充問題:MySQL 遇到過死鎖問題嗎,你是如何解決的?
4.死鎖
死鎖產生:

  • 死鎖是指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而導致惡性循環
  • 當事務試圖以不同的順序鎖定資源時,就可能產生死鎖。多個事務同時鎖定同一個資源時也可能會產生死鎖
  • 鎖的行爲和順序和存儲引擎相關。以同樣的順序執行語句,有些存儲引擎會產生死鎖有些不會——死鎖有雙重原因:真正的數據衝突;存儲引擎的實現方式。

**檢測死鎖:**數據庫系統實現了各種死鎖檢測和死鎖超時的機制。InnoDB存儲引擎能檢測到死鎖的循環依賴並立即返回一個錯誤。

**死鎖恢復:**死鎖發生以後,只有部分或完全回滾其中一個事務,才能打破死鎖,InnoDB目前處理死鎖的方法是,將持有最少行級排他鎖的事務進行回滾。所以事務型應用程序在設計時必須考慮如何處理死鎖,多數情況下只需要重新執行因死鎖回滾的事務即可。

**外部鎖的死鎖檢測:**發生死鎖後,InnoDB 一般都能自動檢測到,並使一個事務釋放鎖並回退,另一個事務獲得鎖,繼續完成事務。但在涉及外部鎖,或涉及表鎖的情況下,InnoDB 並不能完全自動檢測到死鎖, 這需要通過設置鎖等待超時參數 innodb_lock_wait_timeout 來解決

**死鎖影響性能:**死鎖會影響性能而不是會產生嚴重錯誤,因爲InnoDB會自動檢測死鎖狀況並回滾其中一個受影響的事務。在高併發系統上,當許多線程等待同一個鎖時,死鎖檢測可能導致速度變慢。有時當發生死鎖時,禁用死鎖檢測(使用innodb_deadlock_detect配置選項)可能會更有效,這時可以依賴innodb_lock_wait_timeout設置進行事務回滾。

MyISAM避免死鎖:
在自動加鎖的情況下,MyISAM 總是一次獲得 SQL 語句所需要的全部鎖,所以 MyISAM 表不會出現死鎖。
InnoDB避免死鎖:

  • 爲了在單個InnoDB表上執行多個併發寫入操作時避免死鎖,可以在事務開始時通過爲預期要修改的每個元祖(行)使用SELECT ... FOR UPDATE語句來獲取必要的鎖,即使這些行的更改語句是在之後才執行的。
  • 在事務中,如果要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不應先申請共享鎖、更新時再申請排他鎖,因爲這時候當用戶再申請排他鎖時,其他事務可能又已經獲得了相同記錄的共享鎖,從而造成鎖衝突,甚至死鎖
  • 如果事務需要修改或鎖定多個表,則應在每個事務中以相同的順序使用加鎖語句。在應用中,如果不同的程序會併發存取多個表,應儘量約定以相同的順序來訪問表,這樣可以大大降低產生死鎖的機會
  • 通過SELECT ... LOCK IN SHARE MODE獲取行的讀鎖後,如果當前事務再需要對該記錄進行更新操作,則很有可能造成死鎖。
  • 改變事務隔離級別
    如果出現死鎖,可以用 show engine innodb status;命令來確定最後一個死鎖產生的原因。返回結果中包括死鎖相關事務的詳細信息,如引發死鎖的 SQL 語句,事務已經獲得的鎖,正在等待什麼鎖,以及被回滾的事務等。據此可以分析死鎖產生的原因和改進措施。

MySQL調優

  1. 日常工作中你是怎麼優化SQL的?SQL優化的一般步驟是什麼,怎麼看執行計劃(explain),如何理解其中各個字段的含義?如何寫sql能夠有效的使用到複合索引?一條sql執行過長的時間,你如何優化,從哪些方面入手?什麼是最左前綴原則?什麼是最左匹配原則?
    1. 影響MySQL的性能因素:
  • 業務需求對MySQL的影響(合適合度)
  • 存儲定位對MySQL的影響
    - 系統各種配置及規則數據
    - 活躍用戶的基本信息數據
    - 活躍用戶的個性化定製信息數據
    - 準實時的統計信息數據
    - 其他一些訪問頻繁但變更較少的數據
    - 二進制多媒體數據
    - 流水隊列數據‘
    - 超大文本數據
    - 不適合放進MySQL的數據
    - 需要放進緩存的數據
  • Schema設計對系統的性能影響
    - 儘量減少對數據庫訪問的請求
    - 儘量減少無用數據的查詢請求
  • 硬件環境對系統性能的影響
    2.性能分析
    1)MySQL Query Optimizer
  • MySQL 中有專門負責優化 SELECT 語句的優化器模塊,主要功能:通過計算分析系統中收集到的統計信息,爲客戶端請求的 Query 提供他認爲最優的執行計劃(他認爲最優的數據檢索方式,但不見得是 DBA 認爲是最優的,這部分最耗費時間)
  • 當客戶端向 MySQL 請求一條 Query,命令解析器模塊完成請求分類,區別出是 SELECT 並轉發給 MySQL Query Optimize r時,MySQL Query Optimizer 首先會對整條 Query 進行優化,處理掉一些常量表達式的預算,直接換算成常量值。並對 Query 中的查詢條件進行簡化和轉換,如去掉一些無用或顯而易見的條件、結構調整等。然後分析 Query 中的 Hint 信息(如果有),看顯示 Hint 信息是否可以完全確定該 Query 的執行計劃。如果沒有 Hint 或 Hint 信息還不足以完全確定執行計劃,則會讀取所涉及對象的統計信息,根據 Query 進行寫相應的計算分析,然後再得出最後的執行計劃。
    2)MySQL常見瓶頸
  • CPU:CPU在飽和的時候一般發生在數據裝入內存或從磁盤上讀取數據時候
  • IO:磁盤I/O瓶頸發生在裝入數據遠大於內存容量的時候
  • 服務器硬件的性能瓶頸:top,free,iostat 和 vmstat來查看系統的性能狀態
    3)性能下降SQL慢 執行時間長 等待時間長 原因分析
  • 查詢語句寫的爛
  • 索引失效(單值、複合)
  • 關聯查詢太多join(設計缺陷或不得已的需求)
  • 服務器調優及各個參數設置(緩衝、線程數等)
    4)MySQL常見性能分析手段
    在優化MySQL時,通常需要對數據庫進行分析,常見的分析手段有慢查詢日誌EXPLAIN 分析查詢profiling分析以及show命令查詢系統狀態及系統變量,通過定位分析性能的瓶頸,才能更好的優化數據庫系統的性能。
    性能瓶頸定位
    我們可以通過 show 命令查看 MySQL 狀態及變量,找到系統的瓶頸:
Mysql> show status ——顯示狀態信息(擴展show status like ‘XXX’)
Mysql> show variables ——顯示系統變量(擴展show variables like ‘XXX’)
Mysql> show innodb status ——顯示InnoDB存儲引擎的狀態
Mysql> show processlist ——查看當前SQL執行,包括執行狀態、是否鎖表等
Shell> mysqladmin variables -u username -p password——顯示系統變量
Shell> mysqladmin extended-status -u username -p password——顯示狀態信息

Explain(執行計劃)
是什麼:使用 Explain 關鍵字可以模擬優化器執行SQL查詢語句,從而知道 MySQL 是如何處理你的 SQL 語句的。分析你的查詢語句或是表結構的性能瓶頸。
能幹什麼:

  • 表的讀取順序
  • 數據讀取操作的操作類型
  • 哪些索引可以使用
  • 哪些索引被實際使用
  • 表之間的引用
  • 每張表有多少行被優化器查詢
    怎麼玩:
  • Explain + SQL語句
  • 執行計劃包含的信息(如果有分區表的話還會有partitions)
    explain結果格式
    各字段解釋
  • id(select 查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序)
    • id相同,執行順序從上往下
    • id全不同,如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行
    • id部分相同,執行順序是先按照數字大的先執行,然後數字相同的按照從上往下的順序執行
  • select_type(查詢類型,用於區別普通查詢、聯合查詢、子查詢等複雜查詢)
    • SIMPLE :簡單的select查詢,查詢中不包含子查詢或UNION
    • PRIMARY:查詢中若包含任何複雜的子部分,最外層查詢被標記爲PRIMARY
    • SUBQUERY:在select或where列表中包含了子查詢
    • DERIVED:在from列表中包含的子查詢被標記爲DERIVED,MySQL會遞歸執行這些子查詢,把結果放在臨時表裏
    • UNION:若第二個select出現在UNION之後,則被標記爲UNION,若UNION包含在from子句的子查詢中,外層select將被標記爲 DERIVED
    • UNION RESULT:從UNION表獲取結果的select
  • table(顯示這一行的數據是關於哪張表的)
  • type(顯示查詢使用了那種類型,從最好到最差依次排列 system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL )
    tip: 一般來說,得保證查詢至少達到range級別,最好到達ref
    • system:表只有一行記錄(等於系統表),是 const 類型的特例,平時不會出現
    • const:表示通過索引一次就找到了,const 用於比較 primary key 或 unique 索引,因爲只要匹配一行數據,所以很快,如將主鍵置於 where 列表中,mysql 就能將該查詢轉換爲一個常量
    • eq_ref:唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配,常見於主鍵或唯一索引掃描
    • ref:非唯一性索引掃描,範圍匹配某個單獨值得所有行。本質上也是一種索引訪問,他返回所有匹配某個單獨值的行,然而,它可能也會找到多個符合條件的行,多以他應該屬於查找和掃描的混合體
    • range:只檢索給定範圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引,一般就是在你的where語句中出現了between、<、>、in等的查詢,這種範圍掃描索引比全表掃描要好,因爲它只需開始於索引的某一點,而結束於另一點,不用掃描全部索引
    • index:Full Index Scan,index於ALL區別爲index類型只遍歷索引樹。通常比ALL快,因爲索引文件通常比數據文件小。(也就是說雖然all和index都是讀全表,但index是從索引中讀取的,而all是從硬盤中讀的)
    • ALL:Full Table Scan,將遍歷全表找到匹配的行
  • possible_keys(顯示可能應用在這張表中的索引,一個或多個,查詢涉及到的字段若存在索引,則該索引將被列出,但不一定被查詢實際使用)
  • key
    • 實際使用的索引,如果爲NULL,則沒有使用索引
    • 查詢中若使用了覆蓋索引,則該索引和查詢的 select 字段重疊,僅出現在key列表中
      explain-key的查詢示例
  • key_len
    表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度。在不損失精確性的情況下,長度越短越好
    • key_len顯示的值爲索引字段的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的
    • ref(顯示索引的哪一列被使用了,如果可能的話,是一個常數。哪些列或常量被用於查找索引列上的值)
  • rows(根據表統計信息及索引選用情況,大致估算找到所需的記錄所需要讀取的行數)
  • Extra(包含不適合在其他列中顯示但十分重要的額外信息)
    1. using filesort: 說明mysql會對數據使用一個外部的索引排序,不是按照表內的索引順序進行讀取。**mysql中無法利用索引完成的排序操作稱爲“文件排序”。**常見於order by和group by語句中
    2. Using temporary:使用了臨時表保存中間結果,mysql在對查詢結果排序時使用臨時表。常見於排序order by和分組查詢group by。
    3. using index:表示相應的select操作中使用了覆蓋索引,避免訪問了表的數據行,效率不錯,如果同時出現using where,表明索引被用來執行索引鍵值的查找;否則索引被用來讀取數據而非執行查找操作
    4. using where:使用了where過濾
    5. using join buffer:使用了連接緩存
    6. impossible where:where子句的值總是false,不能用來獲取任何元祖
    7. select tables optimized away:在沒有group by子句的情況下,基於索引優化操作或對於MyISAM存儲引擎優化COUNT(*)操作,不必等到執行階段再進行計算,查詢執行計劃生成的階段即完成優化
    8. distinct:優化distinct操作,在找到第一匹配的元祖後即停止找同樣值的動作
      示例:
      explain示例
      1 第一行(執行順序4):id列爲1,表示是union裏的第一個select,select_type列的primary表示該查詢爲外層查詢,table列被標記爲,表示查詢結果來自一個衍生表,其中derived3中3代表該查詢衍生自第三個select查詢,即id爲3的select。【select d1.name…】
      2 第二行(執行順序2):id爲3,是整個查詢中第三個select的一部分。因查詢包含在from中,所以爲derived。【select id,name from t1 where other_column=’’】
      3 第三行(執行順序3):select列表中的子查詢select_type爲subquery,爲整個查詢中的第二個select。【select id from t3】
      4 第四行(執行順序1):select_type爲union,說明第四個select是union裏的第二個select,最先執行【select name,id from t2】
      5 第五行(執行順序5):代表從union的臨時表中讀取行的階段,table列的<union1,4>表示用第一個和第四個select的結果進行union操作。【兩個結果union操作】

慢查詢日誌
MySQL 的慢查詢日誌是 MySQL 提供的一種日誌記錄,它用來記錄在 MySQL 中響應時間超過閾值的語句,具體指運行時間超過 long_query_time 值的 SQL,則會被記錄到慢查詢日誌中。

  • long_query_time 的默認值爲10,意思是運行10秒以上的語句
  • 默認情況下,MySQL數據庫沒有開啓慢查詢日誌,需要手動設置參數開啓
    查看開啓狀態
mysql> show variables like '%slow_query_log%';

開啓慢查詢日誌

  • 臨時配置:
mysql> set global slow_query_log='ON';
mysql> set global slow_query_log_file='/var/lib/mysql/hostname-slow.log';
mysql> set global long_query_time=2;

也可set文件位置,系統會默認給一個缺省文件host_name-slow.log
使用set操作開啓慢查詢日誌只對當前數據庫生效,如果MySQL重啓則會失效

  • 永久配置
    修改配置文件my.cnf或my.ini,在[mysqld]一行下面加入兩個配置參數
[mysqld]
slow_query_log = ON
slow_query_log_file = /var/lib/mysql/hostname-slow.log
long_query_time = 3

注:log-slow-queries 參數爲慢查詢日誌存放的位置,一般這個目錄要有 MySQL 的運行帳號的可寫權限,一般都將這個目錄設置爲 MySQL 的數據存放目錄;long_query_time=2 中的 2 表示查詢超過兩秒才記錄;在my.cnf或者 my.ini 中添加 log-queries-not-using-indexes 參數,表示記錄下沒有使用索引的查詢。
可以用 select sleep(4) 驗證是否成功開啓。
在生產環境中,如果手工分析日誌,查找、分析SQL,還是比較費勁的,所以MySQL提供了日誌分析工具mysqldumpslow。通過 mysqldumpslow --help 查看操作幫助信息:

  • 得到返回記錄集最多的10個SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log
  • 得到訪問次數最多的10個SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/hostname-slow.log
  • 得到按照時間排序的前10條裏面含有左連接的查詢語句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/hostname-slow.log
  • 也可以和管道配合使用
mysqldumpslow -s r -t 10 /var/lib/mysql/hostname-slow.log | more

也可使用 pt-query-digest 分析 RDS MySQL 慢查詢日誌

Show Profile 分析查詢
通過慢日誌查詢可以知道哪些 SQL 語句執行效率低下,通過 explain 我們可以得知 SQL 語句的具體執行情況,索引使用等,還可以結合Show Profile命令查看執行狀態。

  • Show Profile 是 MySQL 提供可以用來分析當前會話中語句執行的資源消耗情況。可以用於SQL的調優的測量
  • 默認情況下,參數處於關閉狀態,並保存最近15次的運行結果
  • 分析步驟
    mysql> show profiles; ±---------±-----------±--------------------------------+ | Query_ID | Duration | Query | ±---------±-----------±--------------------------------+ | 1 | 0.00385450 | show variables like “profiling” | | 2 | 0.00170050 | show variables like “profiling” | | 3 | 0.00038025 | select * from t_base_user | ±---------±-----------±--------------------------------+
  • converting HEAP to MyISAM 查詢結果太大,內存都不夠用了往磁盤上搬了。
  • create tmp table 創建臨時表,這個要注意
  • Copying to tmp table on disk 把內存臨時表複製到磁盤
  • locked
  • 診斷SQL,show profile cpu,block io for query id(上一步前面的問題SQL數字號碼)
  • 日常開發需要注意的結論
1.是否支持,看看當前的mysql版本是否支持
mysql>Show  variables like 'profiling';  --默認是關閉,使用前需要開啓
2.開啓功能,默認是關閉,使用前需要開啓
mysql>set profiling=1;
3.運行SQL
4.查看結果

補充問題:查詢中哪些情況不會使用索引?
5)性能優化
索引優化
1.全值匹配我最愛
2.最佳左前綴法則,比如建立了一個聯合索引(a,b,c),那麼其實我們可利用的索引就有(a), (a,b), (a,b,c)
3.不在索引列上做任何操作(計算、函數、(自動or手動)類型轉換),會導致索引失效而轉向全表掃描
4.存儲引擎不能使用索引中範圍條件右邊的列
5.儘量使用覆蓋索引(只訪問索引的查詢(索引列和查詢列一致)),減少select
6.is null ,is not null 也無法使用索引
7.like “xxxx%” 是可以用到索引的,like “%xxxx” 則不行(like “%xxx%” 同理)。like以通配符開頭(’%abc…’)索引失效會變成全表掃描的操作,
8.字符串不加單引號索引失效
9.少用or,用它來連接時會索引失效
10.<,<=,=,>,>=,BETWEEN,IN 可用到索引,<>,not in ,!= 則不行,會導致全表掃描
一般性建議

  • 對於單鍵索引,儘量選擇針對當前query過濾性更好的索引
  • 在選擇組合索引的時候,當前Query中過濾性最好的字段在索引字段順序中,位置越靠前越好。
  • 在選擇組合索引的時候,儘量選擇可以能夠包含當前query中的where字句中更多字段的索引
  • 儘可能通過分析統計信息和調整query的寫法來達到選擇合適索引的目的
  • 少用Hint強制索引

查詢優化
永遠小標驅動大表(小的數據集驅動大的數據集)

slect * from A where id in (select id from B)`等價於
#等價於
select id from B
select * from A where A.id=B.id

當 B 表的數據集必須小於 A 表的數據集時,用 in 優於 exists

select * from A where exists (select 1 from B where B.id=A.id)
#等價於
select * from A
select * from B where B.id = A.id`

當 A 表的數據集小於B表的數據集時,用 exists優於用 in
注意:A表與B表的ID字段應建立索引。

order by關鍵字優化

  • order by子句,儘量使用 Index 方式排序,避免使用 FileSort 方式排序
  • MySQL 支持兩種方式的排序,FileSort 和 Index,Index效率高,它指 MySQL 掃描索引本身完成排序,FileSort 效率較低;
  • ORDER BY 滿足兩種情況,會使用Index方式排序;①ORDER BY語句使用索引最左前列 ②使用where子句與ORDER BY子句條件列組合滿足索引最左前列
  • 儘可能在索引列上完成排序操作,遵照索引建的最佳最前綴
  • 如果不在索引列上,filesort 有兩種算法,mysql就要啓動雙路排序和單路排序
    • 雙路排序:MySQL 4.1之前是使用雙路排序,字面意思就是兩次掃描磁盤,最終得到數據
    • 單路排序:從磁盤讀取查詢需要的所有列,按照order by 列在 buffer對它們進行排序,然後掃描排序後的列表進行輸出,效率高於雙路排序
  • 優化策略
    • 增大sort_buffer_size參數的設置
    • 增大max_lencth_for_sort_data參數的設置

GROUP BY關鍵字優化

  • group by實質是先排序後進行分組,遵照索引建的最佳左前綴
  • 當無法使用索引列,增大 max_length_for_sort_data 參數的設置,增大sort_buffer_size參數的設置
  • where高於having,能寫在where限定的條件就不要去having限定了

數據類型優化
MySQL 支持的數據類型非常多,選擇正確的數據類型對於獲取高性能至關重要。不管存儲哪種類型的數據,下面幾個簡單的原則都有助於做出更好的選擇。

  • 更小的通常更好:一般情況下,應該儘量使用可以正確存儲數據的最小數據類型
    簡單就好:簡單的數據類型通常需要更少的CPU週期。例如,整數比字符操作代價更低,因爲字符集和校對規則(排序規則)使字符比較比整型比較複雜。
  • 儘量避免NULL:通常情況下最好指定列爲NOT NULL

MySQL分區、分表、分庫

  1. MySQL分區
    一般情況下我們創建的表對應一組存儲文件,使用MyISAM存儲引擎時是一個.MYI.MYD文件,使用Innodb存儲引擎時是一個.ibd.frm(表結構)文件。
    當數據量較大時(一般千萬條記錄級別以上),MySQL的性能就會開始下降,這時我們就需要將數據分散到多組存儲文件,保證其單個文件的執行效率
    能幹嘛
  • 邏輯數據分割
  • 提高單一的寫和讀應用速度
  • 提高分區範圍讀查詢的速度
  • 分割數據能夠有多個不同的物理文件路徑
  • 高效的保存歷史數據

怎麼玩
首先查看當前數據庫是否支持分區

  • MySQL5.6以及之前版本:
SHOW VARIABLES LIKE '%partition%';
  • MySQL5.6:
show plugins;

分區類型及操作

  • RANGE分區:基於屬於一個給定連續區間的列值,把多行分配給分區。mysql將會根據指定的拆分策略,,把數據放在不同的表文件上。相當於在文件上,被拆成了小塊.但是,對外給客戶的感覺還是一張表,透明的。

按照 range 來分,就是每個庫一段連續的數據,這個一般是按比如時間範圍來的,比如交易表啊,銷售表啊等,可以根據年月來存放數據。可能會產生熱點問題,大量的流量都打在最新的數據上了。
range 來分,好處在於說,擴容的時候很簡單。

  • LIST分區:類似於按RANGE分區,每個分區必須明確定義。它們的主要區別在於,LIST分區中每個分區的定義和選擇是基於某列的值從屬於一個值列表集中的一個值,而RANGE分區是從屬於一個連續區間值的集合。

  • HASH分區基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數可以包含MySQL 中有效的、產生非負整數值的任何表達式。
    hash 分發,好處在於說,可以平均分配每個庫的數據量和請求壓力;壞處在於說擴容起來比較麻煩,會有一個數據遷移的過程,之前的數據需要重新計算 hash 值重新分配到不同的庫或表

  • KEY分區:類似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL服務器提供其自身的哈希函數。必須有一列或多列包含整數值

看上去分區表很帥氣,爲什麼大部分互聯網還是更多的選擇自己分庫分表來水平擴展咧?

  • 分區表,分區鍵設計不太靈活,如果不走分區鍵,很容易出現全表鎖
  • 一旦數據併發量上來,如果在分區表實施關聯,就是一個災難
  • 自己分庫分表,自己掌控業務場景與訪問模式,可控。分區表,研發寫了一個sql,都不確定mysql是怎麼玩的,不太可控

補充問題:隨着業務的發展,業務越來越複雜,應用的模塊越來越多,總的數據量很大,高併發讀寫操作均超過單個數據庫服務器的處理能力怎麼辦?
答:這個時候就出現了數據分片數據分片指按照某個維度將存放在單一數據庫中的數據分散地存放至多個數據庫或表中。數據分片的有效手段就是對關係型數據庫進行分庫和分表
區別於分區的是,分區一般都是放在單機裏的,用的比較多的是時間範圍分區,方便歸檔。只不過分庫分表需要代碼實現分區則是mysql內部實現。分庫分表和分區並不衝突,可以結合使用。
補充問題:說說分庫與分表的設計
1)MySQL分表
分表有兩種分割方式,一種垂直拆分,另一種水平拆分。

  • 垂直拆分
    垂直分表,通常是按照業務功能的使用頻次,把主要的、熱門的字段放在一起做爲主要表。然後把不常用的,按照各自的業務屬性進行聚集,拆分到不同的次要表中;主要表和次要表的關係一般都是一對一的。
  • 水平拆分(數據分片)
    單表的容量不超過500W,否則建議水平拆分。是把一個表複製成同樣表結構的不同表,然後把數據按照一定的規則劃分,分別存儲到這些表中,從而保證單表的容量不會太大,提升性能;當然這些結構一樣的表,可以放在一個或多個數據庫中。
    水平分割的幾種方法:
    1.使用MD5哈希,做法是對UID進行md5加密,然後取前幾位(我們這裏取前兩位),然後就可以將不同的UID哈希到不同的用戶表(user_xx)中了。
    2.還可根據時間放入不同的表,比如:article_201601,article_201602。
    3.按熱度拆分,高點擊率的詞條生成各自的一張表,低熱度的詞條都放在一張大表裏,待低熱度的詞條達到一定的貼數後,再把低熱度的表單獨拆分成一張表。
    4.根據ID的值放入對應的表,第一個表user_0000,第二個100萬的用戶數據放在第二 個表user_0001中,隨用戶增加,直接添加用戶表就行了。
    垂直分表和水平分表
    2)MySQL分庫
    問題:爲什麼要分庫?
    答:數據庫集羣環境後都是多臺 slave,基本滿足了讀取操作; 但是寫入或者說大數據、頻繁的寫入操作對master性能影響就比較大,這個時候,單庫並不能解決大規模併發寫入的問題,所以就會考慮分庫
    問題:分庫是什麼?
    答:一個庫裏表太多了,導致了海量數據,系統性能下降,把原本存儲於一個庫的表拆分存儲到多個庫上, 通常是將表按照功能模塊、關係密切程度劃分出來,部署到不同庫上

優點:

  • 減少增量數據寫入時的鎖對查詢的影響
  • 由於單表數量下降,常見的查詢操作由於減少了需要掃描的記錄,使得單表單次查詢所需的檢索行數變少,減少了磁盤IO,時延變短
    但是它無法解決單表數據量太大的問題

分庫分表後的難題分佈式事務的問題數據的完整性和一致性問題
數據操作維度問題:用戶、交易、訂單各個不同的維度,用戶查詢維度、產品數據分析維度的不同對比分析角度。**跨庫聯合查詢的問題,**可能需要兩次查詢 跨節點的count、order by、group by以及聚合函數問題,可能需要分別在各個節點上得到結果後在應用程序端進行合併。額外的數據管理負擔,如:訪問數據表的導航定位。 額外的數據運算壓力,如:需要在多個節點執行,然後再合併,計算程序編碼開發難度提升,沒有太好的框架解決,更多依賴業務看如何分,如何合,是個難題。

主從複製

  1. 複製的基本原理
  • slave 會從 master 讀取 binlog 來進行數據同步
  • 三個步驟:
    主從複製
    1)master將改變記錄到二進制日誌(binary log)。這些記錄過程叫做二進制日誌事件,binary log events;
    2)salve 將 master 的 binary log events 拷貝到它的中繼日誌(relay log);
    3)slave 重做中繼日誌中的事件,將改變應用到自己的數據庫中。MySQL 複製是異步且是串行化的

複製的基本原則

  • 每個 slave只有一個 master
  • 每個 salve只能有一個唯一的服務器 ID
  • 每個master可以有多個salve

複製的最大問題:延時

其他問題

  1. 說一說三個範式
  • 第一範式(1NF):數據庫表中的字段都是單一屬性的,不可再分。這個單一屬性由基本類型構成,包括整型、實數、字符型、邏輯型、日期型等。
  • 第二範式(2NF):數據庫表中不存在非關鍵字段對任一候選關鍵字段的部分函數依賴(部分函數依賴指的是存在組合關鍵字中的某些字段決定非關鍵字段的情況),也即所有非關鍵字段都完全依賴於任意一組候選關鍵字
  • 第三範式(3NF):在第二範式的基礎上,數據表中如果不存在非關鍵字段對任一候選關鍵字段的傳遞函數依賴則符合第三範式。所謂傳遞函數依賴,指的是如 果存在"A → B → C"的決定關係,則C傳遞函數依賴於A。因此,滿足第三範式的數據庫表應該不存在如下依賴關係:關鍵字段 → 非關鍵字段 x → 非關鍵字段y
  1. 百萬級別或以上的數據如何刪除
    關於索引:由於索引需要額外的維護成本,因爲索引文件是單獨存在的文件,所以當我們對數據的增加,修改,刪除,都會產生額外的對索引文件的操作,這些操作需要消耗額外的IO,會降低增/改/刪的執行效率。所以,在我們刪除數據庫百萬級別數據的時候,查詢MySQL官方手冊得知刪除數據的速度和創建的索引數量是成正比的。所以我們想要刪除百萬數據的時候:
    1)可以先刪除索引(此時大概耗時三分多鐘)
    2)然後刪除其中無用數據(此過程需要不到兩分鐘)
    3)刪除完成後重新創建索引(此時數據較少了)創建索引也非常快,約十分鐘左右。
    與之前的直接刪除絕對是要快速很多,更別說萬一刪除中斷,一切刪除會回滾。那更是坑了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章