1. 程式人生 > >MySQL/資料庫 知識點總結

MySQL/資料庫 知識點總結

 

書籍推薦

  • 《SQL基礎教程(第2版)》 (入門級)
  • 《高效能MySQL : 第3版》 (進階)

文字教程推薦

  • SQL Tutorial (SQL語句學習,英文)、SQL Tutorial(SQL語句學習,中文)、SQL語句線上練習 (非常不錯)
  • Github-MySQL入門教程(MySQL tutorial book) (從零開始學習MySQL,主要是面向MySQL資料庫管理系統初學者)
  • 官方教程
  • MySQL 教程(菜鳥教程)

相關資源推薦

  • 中國5級行政區域mysql庫

視訊教程推薦

基礎入門: 與MySQL的零距離接觸-慕課網

MySQL開發技巧: MySQL開發技巧(一)  MySQL開發技巧(二)  MySQL開發技巧(三)

MySQL5.7新特性及相關優化技巧: MySQL5.7版本新特性  效能優化之MySQL優化

MySQL叢集(PXC)入門  MyCAT入門及應用

常見問題總結

什麼是MySQL?

MySQL 是一種關係型資料庫,在Java企業級開發中非常常用,因為 MySQL 是開源免費的,並且方便擴充套件。阿里巴巴資料庫系統也大量用到了 MySQL,因此它的穩定性是有保障的。MySQL是開放原始碼的,因此任何人都可以在 GPL(General Public License) 的許可下下載並根據個性化的需要對其進行修改。MySQL的預設埠號是3306。

儲存引擎

一些常用命令

檢視MySQL提供的所有儲存引擎

mysql> show engines;

從上圖我們可以查看出 MySQL 當前預設的儲存引擎是InnoDB,並且在5.7版本所有的儲存引擎中只有 InnoDB 是事務性儲存引擎,也就是說只有 InnoDB 支援事務。

檢視MySQL當前預設的儲存引擎

我們也可以通過下面的命令檢視預設的儲存引擎。

mysql> show variables like '%storage_engine%';

查看錶的儲存引擎

show table status like "table_name" ;

MyISAM和InnoDB區別

MyISAM是MySQL的預設資料庫引擎(5.5版之前)。雖然效能極佳,而且提供了大量的特性,包括全文索引、壓縮、空間函式等,但MyISAM不支援事務和行級鎖,而且最大的缺陷就是崩潰後無法安全恢復。不過,5.5版本之後,MySQL引入了InnoDB(事務性資料庫引擎),MySQL 5.5版本後預設的儲存引擎為InnoDB。

大多數時候我們使用的都是 InnoDB 儲存引擎,但是在某些情況下使用 MyISAM 也是合適的比如讀密集的情況下。(如果你不介意 MyISAM 崩潰恢復問題的話)。

兩者的對比:

  1. 是否支援行級鎖 : MyISAM 只有表級鎖(table-level locking),而InnoDB 支援行級鎖(row-level locking)和表級鎖,預設為行級鎖。
  2. 是否支援事務和崩潰後的安全恢復: MyISAM 強調的是效能,每次查詢具有原子性,其執行速度比InnoDB型別更快,但是不提供事務支援。但是InnoDB 提供事務支援事務,外部鍵等高階資料庫功能。 具有事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。
  3. 是否支援外來鍵: MyISAM不支援,而InnoDB支援。
  4. 是否支援MVCC :僅 InnoDB 支援。應對高併發事務, MVCC比單純的加鎖更高效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 兩個隔離級別下工作;MVCC可以使用 樂觀(optimistic)鎖 和 悲觀(pessimistic)鎖來實現;各資料庫中MVCC實現並不統一。推薦閱讀:MySQL-InnoDB-MVCC多版本併發控制
  5. ......

《MySQL高效能》上面有一句話這樣寫到:

不要輕易相信“MyISAM比InnoDB快”之類的經驗之談,這個結論往往不是絕對的。在很多我們已知場景中,InnoDB的速度都可以讓MyISAM望塵莫及,尤其是用到了聚簇索引,或者需要訪問的資料都可以放入記憶體的應用。

一般情況下我們選擇 InnoDB 都是沒有問題的,但是某些情況下你並不在乎可擴充套件能力和併發能力,也不需要事務支援,也不在乎崩潰後的安全恢復問題的話,選擇MyISAM也是一個不錯的選擇。但是一般情況下,我們都是需要考慮到這些問題的。

字符集及校對規則

字符集指的是一種從二進位制編碼到某類字元符號的對映。校對規則則是指某種字符集下的排序規則。MySQL中每一種字符集都會對應一系列的校對規則。

MySQL採用的是類似繼承的方式指定字符集的預設值,每個資料庫以及每張資料表都有自己的預設值,他們逐層繼承。比如:某個庫中所有表的預設字符集將是該資料庫所指定的字符集(這些表在沒有指定字符集的情況下,才會採用預設字符集) PS:整理自《Java工程師修煉之道》

詳細內容可以參考: MySQL字符集及校對規則的理解

索引

MySQL索引使用的資料結構主要有BTree索引 和 雜湊索引 。對於雜湊索引來說,底層的資料結構就是雜湊表,因此在絕大多數需求為單條記錄查詢的時候,可以選擇雜湊索引,查詢效能最快;其餘大部分場景,建議選擇BTree索引。

MySQL的BTree索引使用的是B樹中的B+Tree,但對於主要的兩種儲存引擎的實現方式是不同的。

  • MyISAM: B+Tree葉節點的data域存放的是資料記錄的地址。在索引檢索的時候,首先按照B+Tree搜尋演算法搜尋索引,如果指定的Key存在,則取出其 data 域的值,然後以 data 域的值為地址讀取相應的資料記錄。這被稱為“非聚簇索引”。
  • InnoDB: 其資料檔案本身就是索引檔案。相比MyISAM,索引檔案和資料檔案是分離的,其表資料檔案本身就是按B+Tree組織的一個索引結構,樹的葉節點data域儲存了完整的資料記錄。這個索引的key是資料表的主鍵,因此InnoDB表資料檔案本身就是主索引。這被稱為“聚簇索引(或聚集索引)”。而其餘的索引都作為輔助索引,輔助索引的data域儲存相應記錄主鍵的值而不是地址,這也是和MyISAM不同的地方。在根據主索引搜尋時,直接找到key所在的節點即可取出資料;在根據輔助索引查詢時,則需要先取出主鍵的值,再走一遍主索引。 因此,在設計表的時候,不建議使用過長的欄位作為主鍵,也不建議使用非單調的欄位作為主鍵,這樣會造成主索引頻繁分裂。 PS:整理自《Java工程師修煉之道》

更多關於索引的內容可以檢視文件首頁MySQL目錄下關於索引的詳細總結。

查詢快取的使用

執行查詢語句的時候,會先查詢快取。不過,MySQL 8.0 版本後移除,因為這個功能不太實用

my.cnf加入以下配置,重啟MySQL開啟查詢快取

query_cache_type=1
query_cache_size=600000

MySQL執行以下命令也可以開啟查詢快取

set global  query_cache_type=1;
set global  query_cache_size=600000;

如上,開啟查詢快取後在同樣的查詢條件以及資料情況下,會直接在快取中返回結果。這裡的查詢條件包括查詢本身、當前要查詢的資料庫、客戶端協議版本號等一些可能影響結果的資訊。因此任何兩個查詢在任何字元上的不同都會導致快取不命中。此外,如果查詢中包含任何使用者自定義函式、儲存函式、使用者變數、臨時表、MySQL庫中的系統表,其查詢結果也不會被快取。

快取建立之後,MySQL的查詢快取系統會跟蹤查詢中涉及的每張表,如果這些表(資料或結構)發生變化,那麼和這張表相關的所有快取資料都將失效。

快取雖然能夠提升資料庫的查詢效能,但是快取同時也帶來了額外的開銷,每次查詢後都要做一次快取操作,失效後還要銷燬。 因此,開啟快取查詢要謹慎,尤其對於寫密集的應用來說更是如此。如果開啟,要注意合理控制快取空間大小,一般來說其大小設定為幾十MB比較合適。此外,還可以通過sql_cache和sql_no_cache來控制某個查詢語句是否需要快取:

select sql_no_cache count(*) from usr;

什麼是事務?

事務是邏輯上的一組操作,要麼都執行,要麼都不執行。

事務最經典也經常被拿出來說例子就是轉賬了。假如小明要給小紅轉賬1000元,這個轉賬會涉及到兩個關鍵操作就是:將小明的餘額減少1000元,將小紅的餘額增加1000元。萬一在這兩個操作之間突然出現錯誤比如銀行系統崩潰,導致小明餘額減少而小紅的餘額沒有增加,這樣就不對了。事務就是保證這兩個關鍵操作要麼都成功,要麼都要失敗。

事物的四大特性(ACID)

  1. 原子性(Atomicity): 事務是最小的執行單位,不允許分割。事務的原子性確保動作要麼全部完成,要麼完全不起作用;
  2. 一致性(Consistency): 執行事務前後,資料保持一致,多個事務對同一個資料讀取的結果是相同的;
  3. 隔離性(Isolation): 併發訪問資料庫時,一個使用者的事務不被其他事務所幹擾,各併發事務之間資料庫是獨立的;
  4. 永續性(Durability): 一個事務被提交之後。它對資料庫中資料的改變是持久的,即使資料庫發生故障也不應該對其有任何影響。

併發事務帶來哪些問題?

在典型的應用程式中,多個事務併發執行,經常會操作相同的資料來完成各自的任務(多個使用者對同一資料進行操作)。併發雖然是必須的,但可能會導致以下的問題。

  • 髒讀(Dirty read): 當一個事務正在訪問資料並且對資料進行了修改,而這種修改還沒有提交到資料庫中,這時另外一個事務也訪問了這個資料,然後使用了這個資料。因為這個資料是還沒有提交的資料,那麼另外一個事務讀到的這個資料是“髒資料”,依據“髒資料”所做的操作可能是不正確的。
  • 丟失修改(Lost to modify): 指在一個事務讀取一個數據時,另外一個事務也訪問了該資料,那麼在第一個事務中修改了這個資料後,第二個事務也修改了這個資料。這樣第一個事務內的修改結果就被丟失,因此稱為丟失修改。 例如:事務1讀取某表中的資料A=20,事務2也讀取A=20,事務1修改A=A-1,事務2也修改A=A-1,最終結果A=19,事務1的修改被丟失。
  • 不可重複讀(Unrepeatableread): 指在一個事務內多次讀同一資料。在這個事務還沒有結束時,另一個事務也訪問該資料。那麼,在第一個事務中的兩次讀資料之間,由於第二個事務的修改導致第一個事務兩次讀取的資料可能不太一樣。這就發生了在一個事務內兩次讀到的資料是不一樣的情況,因此稱為不可重複讀。
  • 幻讀(Phantom read): 幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行資料,接著另一個併發事務(T2)插入了一些資料時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱為幻讀。

不可重複讀和幻讀區別:

不可重複讀的重點是修改比如多次讀取一條記錄發現其中某些列的值被修改,幻讀的重點在於新增或者刪除比如多次讀取一條記錄發現記錄增多或減少了。

事務隔離級別有哪些?MySQL的預設隔離級別是?

SQL 標準定義了四個隔離級別:

  • READ-UNCOMMITTED(讀取未提交): 最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致髒讀、幻讀或不可重複讀。
  • READ-COMMITTED(讀取已提交): 允許讀取併發事務已經提交的資料,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生。
  • REPEATABLE-READ(可重複讀): 對同一欄位的多次讀取結果都是一致的,除非資料是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。
  • SERIALIZABLE(可序列化): 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。

隔離級別髒讀不可重複讀幻影讀
READ-UNCOMMITTED
READ-COMMITTED ×
REPEATABLE-READ × ×
SERIALIZABLE × × ×

MySQL InnoDB 儲存引擎的預設支援的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;命令來檢視

mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+

這裡需要注意的是:與 SQL 標準不同的地方在於 InnoDB 儲存引擎在 REPEATABLE-READ(可重讀) 事務隔離級別下使用的是Next-Key Lock 鎖演算法,因此可以避免幻讀的產生,這與其他資料庫系統(如 SQL Server) 是不同的。所以說InnoDB 儲存引擎的預設支援的隔離級別是 REPEATABLE-READ(可重讀) 已經可以完全保證事務的隔離性要求,即達到了 SQL標準的 SERIALIZABLE(可序列化) 隔離級別。因為隔離級別越低,事務請求的鎖越少,所以大部分資料庫系統的隔離級別都是 READ-COMMITTED(讀取提交內容) ,但是你要知道的是InnoDB 儲存引擎預設使用 REPEAaTABLE-READ(可重讀) 並不會有任何效能損失。

InnoDB 儲存引擎在 分散式事務 的情況下一般會用到 SERIALIZABLE(可序列化) 隔離級別。

鎖機制與InnoDB鎖演算法

MyISAM和InnoDB儲存引擎使用的鎖:

  • MyISAM採用表級鎖(table-level locking)。
  • InnoDB支援行級鎖(row-level locking)和表級鎖,預設為行級鎖

表級鎖和行級鎖對比:

  • 表級鎖: MySQL中鎖定 粒度最大 的一種鎖,對當前操作的整張表加鎖,實現簡單,資源消耗也比較少,加鎖快,不會出現死鎖。其鎖定粒度最大,觸發鎖衝突的概率最高,併發度最低,MyISAM和 InnoDB引擎都支援表級鎖。
  • 行級鎖: MySQL中鎖定 粒度最小 的一種鎖,只針對當前操作的行進行加鎖。 行級鎖能大大減少資料庫操作的衝突。其加鎖粒度最小,併發度高,但加鎖的開銷也最大,加鎖慢,會出現死鎖。

詳細內容可以參考: MySQL鎖機制簡單瞭解一下:https://blog.csdn.net/qq_34337272/article/details/80611486

InnoDB儲存引擎的鎖的演算法有三種:

  • Record lock:單個行記錄上的鎖
  • Gap lock:間隙鎖,鎖定一個範圍,不包括記錄本身
  • Next-key lock:record+gap 鎖定一個範圍,包含記錄本身

相關知識點:

  1. innodb對於行的查詢使用next-key lock
  2. Next-locking keying為了解決Phantom Problem幻讀問題
  3. 當查詢的索引含有唯一屬性時,將next-key lock降級為record key
  4. Gap鎖設計的目的是為了阻止多個事務將記錄插入到同一範圍內,而這會導致幻讀問題的產生
  5. 有兩種方式顯式關閉gap鎖:(除了外來鍵約束和唯一性檢查外,其餘情況僅使用record lock) A. 將事務隔離級別設定為RC B. 將引數innodb_locks_unsafe_for_binlog設定為1

大表優化

當MySQL單表記錄數過大時,資料庫的CRUD效能會明顯下降,一些常見的優化措施如下:

1. 限定資料的範圍

務必禁止不帶任何限制資料範圍條件的查詢語句。比如:我們當用戶在查詢訂單歷史的時候,我們可以控制在一個月的範圍內;

2. 讀/寫分離

經典的資料庫拆分方案,主庫負責寫,從庫負責讀;

3. 垂直分割槽

根據資料庫裡面資料表的相關性進行拆分。 例如,使用者表中既有使用者的登入資訊又有使用者的基本資訊,可以將使用者表拆分成兩個單獨的表,甚至放到單獨的庫做分庫。

簡單來說垂直拆分是指資料表列的拆分,把一張列比較多的表拆分為多張表。 如下圖所示,這樣來說大家應該就更容易理解了。 

  • 垂直拆分的優點: 可以使得列資料變小,在查詢時減少讀取的Block數,減少I/O次數。此外,垂直分割槽可以簡化表的結構,易於維護。
  • 垂直拆分的缺點: 主鍵會出現冗餘,需要管理冗餘列,並會引起Join操作,可以通過在應用層進行Join來解決。此外,垂直分割槽會讓事務變得更加複雜;

4. 水平分割槽

保持資料表結構不變,通過某種策略儲存資料分片。這樣每一片資料分散到不同的表或者庫中,達到了分散式的目的。 水平拆分可以支撐非常大的資料量。

水平拆分是指資料錶行的拆分,表的行數超過200萬行時,就會變慢,這時可以把一張的表的資料拆成多張表來存放。舉個例子:我們可以將使用者資訊表拆分成多個使用者資訊表,這樣就可以避免單一表資料量過大對效能造成影響。

水平拆分可以支援非常大的資料量。需要注意的一點是:分表僅僅是解決了單一表資料過大的問題,但由於表的資料還是在同一臺機器上,其實對於提升MySQL併發能力沒有什麼意義,所以 水平拆分最好分庫 。

水平拆分能夠 支援非常大的資料量儲存,應用端改造也少,但 分片事務難以解決 ,跨節點Join效能較差,邏輯複雜。《Java工程師修煉之道》的作者推薦 儘量不要對資料進行分片,因為拆分會帶來邏輯、部署、運維的各種複雜度 ,一般的資料表在優化得當的情況下支撐千萬以下的資料量是沒有太大問題的。如果實在要分片,儘量選擇客戶端分片架構,這樣可以減少一次和中介軟體的網路I/O。

下面補充一下資料庫分片的兩種常見方案:

  • 客戶端代理: 分片邏輯在應用端,封裝在jar包中,通過修改或者封裝JDBC層來實現。 噹噹網的 Sharding-JDBC 、阿里的TDDL是兩種比較常用的實現。
  • 中介軟體代理: 在應用和資料中間加了一個代理層。分片邏輯統一維護在中介軟體服務中。 我們現在談的 Mycat 、360的Atlas、網易的DDB等等都是這種架構的實現。

詳細內容可以參考: MySQL大表優化方案: https://segmentfault.com/a/1190000006158186

解釋一下什麼是池化設計思想。什麼是資料庫連線池?為什麼需要資料庫連線池?

池話設計應該不是一個新名詞。我們常見的如java執行緒池、jdbc連線池、redis連線池等就是這類設計的代表實現。這種設計會初始預設資源,解決的問題就是抵消每次獲取資源的消耗,如建立執行緒的開銷,獲取遠端連線的開銷等。就好比你去食堂打飯,打飯的大媽會先把飯盛好幾份放那裡,你來了就直接拿著飯盒加菜即可,不用再臨時又盛飯又打菜,效率就高了。除了初始化資源,池化設計還包括如下這些特徵:池子的初始值、池子的活躍值、池子的最大值等,這些特徵可以直接對映到java執行緒池和資料庫連線池的成員屬性中。——這篇文章對池化設計思想介紹的還不錯,直接複製過來,避免重複造輪子了。

資料庫連線本質就是一個 socket 的連線。資料庫服務端還要維護一些快取和使用者許可權資訊之類的 所以佔用了一些記憶體。我們可以把資料庫連線池是看做是維護的資料庫連線的快取,以便將來需要對資料庫的請求時可以重用這些連線。為每個使用者開啟和維護資料庫連線,尤其是對動態資料庫驅動的網站應用程式的請求,既昂貴又浪費資源。**在連線池中,建立連線後,將其放置在池中,並再次使用它,因此不必建立新的連線。如果使用了所有連線,則會建立一個新連線並將其新增到池中。**連線池還減少了使用者必須等待建立與資料庫的連線的時間。

分庫分表之後,id 主鍵如何處理?

因為要是分成多個表之後,每個表都是從 1 開始累加,這樣是不對的,我們需要一個全域性唯一的 id 來支援。

生成全域性 id 有下面這幾種方式:

  • UUID:不適合作為主鍵,因為太長了,並且無序不可讀,查詢效率低。比較適合用於生成唯一的名字的標示比如檔案的名字。
  • 資料庫自增 id : 兩臺資料庫分別設定不同步長,生成不重複ID的策略來實現高可用。這種方式生成的 id 有序,但是需要獨立部署資料庫例項,成本高,還會有效能瓶頸。
  • 利用 redis 生成 id : 效能比較好,靈活方便,不依賴於資料庫。但是,引入了新的元件造成系統更加複雜,可用性降低,編碼更加複雜,增加了系統成本。
  • Twitter的snowflake演算法 :Github 地址:https://github.com/twitter-archive/snowflake。
  • 美團的Leaf分散式ID生成系統 :Leaf 是美團開源的分散式ID生成器,能保證全域性唯一性、趨勢遞增、單調遞增、資訊保安,裡面也提到了幾種分散式方案的對比,但也需要依賴關係資料庫、Zookeeper等中介軟體。感覺還不錯。美團技術團隊的一篇文章:https://tech.meituan.com/2017/04/21/mt-leaf.html 。
  • ......

一條SQL語句在MySQL中如何執行的

一條SQL語句在MySQL中如何執行的

MySQL高效能優化規範建議

MySQL高效能優化規範建議

一條SQL語句執行得很慢的原因有哪些?

騰訊面試:一條SQL語句執行得很慢的原因有哪些?---不看後悔