MySQL(一)基礎知識

Mysql數據庫基本知識

 

什麼是MySQL?

 

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

 

一:存儲引擎

 

一些常用命令

// 查看MySQL提供的所有存儲引擎
 mysql> show engines; 
// 我們也可以通過下面的命令查看默認的存儲引擎。 
mysql> show variables like '%storage_engine%'; 
// 查看錶的存儲引擎 
show table status like "table_name" ;

MySQL 當前默認的存儲引擎是InnoDB,並且在5.7版本所有的存儲引擎中只有 InnoDB 是事務性存儲引擎,也就是說只有 InnoDB 支持事務。查看MySQL當前默認的存儲引擎。

 

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多版本併發控制

 

一般情況下我們選擇 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 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的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。

 

MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;命令來查看

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

START TARNSACTION |BEGIN:顯式地開啓一個事務。
COMMIT:提交事務,使得對數據庫做的所有修改成爲永久性。、
ROLLBACK:回滾會結束用戶的事務,並撤銷正在進行的所有未提交的修改。

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

 

 

三:鎖機制與InnoDB鎖算法

 

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

 

  • MyISAM採用表級鎖(table-level locking)。
  • InnoDB支持行級鎖(row-level locking)和表級鎖,默認爲行級鎖

 

表級鎖和行級鎖對比:

 

  • 表級鎖: MySQL中鎖定 粒度最大 的一種鎖,對當前操作的整張表加鎖, MyISAM和 InnoDB引擎都支持表級鎖。
  • 行級鎖: MySQL中鎖定 粒度最小 的一種鎖,只針對當前操作的行進行加鎖。 行級鎖能大大減少數據庫操作的衝突。其加鎖粒度最小,併發度高,但加鎖的開銷也最大,加鎖慢,會出現死鎖。

 

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(READ-COMMITTED) 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等中間件。感覺還不錯。

美團技術團隊的一篇文章。

 

七:阿里巴巴Java開發手冊關於數據庫的注意事項

 

 

模糊查詢:

【強制】頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。

說明:索引文件具有 B-Tree 的最左前綴匹配特性,如果左邊的值未確定,那麼無法使用此索引。

 

外鍵和級聯:

【強制】不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。

  1. 增加了複雜性: a.每次做DELETE 或者UPDATE都必須考慮外鍵約束,會導致開發的時候很痛苦,測試數據極爲不方便;b.外鍵的主從關係是定的,假如那天需求有變化,數據庫中的這個字段根本不需要和其他表有關聯的話就會增加很多麻煩。
  2. 增加了額外工作: 數據庫需要增加維護外鍵的工作,比如當我們做一些涉及外鍵字段的增,刪,更新操作之後,需要觸發相關操作去檢查,保證數據的的一致性和正確性,這樣會不得不消耗資源;(個人覺得這個不是不用外鍵的原因,因爲即使你不使用外鍵,你在應用層面也還是要保證的。所以,我覺得這個影響可以忽略不計。)
  3. 外鍵還會因爲需要請求對其他表內部加鎖而容易出現死鎖情況
  4. 對分庫分表不友好 :因爲分庫分表下外鍵是無法生效的。

你們的老婆來了!

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