標題有點標題黨的意思,看了文章以後但願你們不會有這個想法,絕對乾貨!!!這篇花文章是我花了幾天時間對以前總結的MySQL知識點作了完善後的產物,這篇文章能夠用來回顧MySQL基礎知識以及備戰MySQL常見面試問題。java
文末有公衆號二維碼,歡迎關注獲取筆主最新更新文章,並可免費獲取筆主總結的《Java面試突擊》以及Java工程師必備學習資源。
@[toc]mysql
MySQL 是一種關係型數據庫,在Java企業級開發中很是經常使用,由於 MySQL 是開源免費的,而且方便擴展。阿里巴巴數據庫系統也大量用到了 MySQL,所以它的穩定性是有保障的。MySQL是開放源代碼的,所以任何人均可以在 GPL(General Public License) 的許可下下載並根據個性化的須要對其進行修改。MySQL的默認端口號是3306。面試
事務是邏輯上的一組操做,要麼都執行,要麼都不執行。算法
事務最經典也常常被拿出來講例子就是轉帳了。假如小明要給小紅轉帳1000元,這個轉帳會涉及到兩個關鍵操做就是:將小明的餘額減小1000元,將小紅的餘額增長1000元。萬一在這兩個操做之間忽然出現錯誤好比銀行系統崩潰,致使小明餘額減小而小紅的餘額沒有增長,這樣就不對了。事務就是保證這兩個關鍵操做要麼都成功,要麼都要失敗。sql
在典型的應用程序中,多個事務併發運行,常常會操做相同的數據來完成各自的任務(多個用戶對統一數據進行操做)。併發雖然是必須的,但可能會致使如下的問題。數據庫
不可重複度和幻讀區別:編程
不可重複讀的重點是修改,幻讀的重點在於新增或者刪除。segmentfault
例1(一樣的條件, 你讀取過的數據, 再次讀取出來發現值不同了 ):事務1中的A先生讀取本身的工資爲 1000的操做還沒完成,事務2中的B先生就修改了A的工資爲2000,導 致A再讀本身的工資時工資變爲 2000;這就是不可重複讀。安全
例2(一樣的條件, 第1次和第2次讀出來的記錄數不同 ):假某工資單表中工資大於3000的有4人,事務1讀取了全部工資大於3000的人,共查到4條記錄,這時事務2 又插入了一條工資大於3000的記錄,事務1再次讀取時查到的記錄就變爲了5條,這樣就致使了幻讀。性能優化
SQL 標準定義了四個隔離級別:
隔離級別 | 髒讀 | 不可重複讀 | 幻影讀 |
---|---|---|---|
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 存儲引擎默認使用 REPEATABLE-READ(可重讀)並不會有任何性能損失。
InnoDB 存儲引擎在 分佈式事務 的狀況下通常會用到SERIALIZABLE(可串行化)隔離級別。
![[思惟導圖-索引篇]](http://my-blog-to-use.oss-cn-...
如下內容整理自: 地址: https://juejin.im/post/5b55b8... 做者 :Java3y
先從 MySQL 的基本存儲結構提及
MySQL的基本存儲結構是頁(記錄都存在頁裏邊):
每一個數據頁中的記錄又能夠組成一個單向鏈表
- 每一個數據頁都會爲存儲在它裏邊兒的記錄生成一個頁目錄,在經過主鍵查找某條記錄的時候能夠在頁目錄中使用二分法快速定位到對應的槽,而後再遍歷該槽對應分組中的記錄便可快速找到指定的記錄 - 以其餘列(非主鍵)做爲搜索條件:只能從最小記錄開始依次遍歷單鏈表中的每條記錄。
因此說,若是咱們寫select * from user where indexname = 'xxx'這樣沒有進行任何優化的sql語句,默認會這樣作:
很明顯,在數據量很大的狀況下這樣查找會很慢!這樣的時間複雜度爲O(n)。
索引作了些什麼可讓咱們查詢加快速度呢?其實就是將無序的數據變成有序(相對):
要找到id爲8的記錄簡要步驟:
很明顯的是:沒有用索引咱們是須要遍歷雙向鏈表來定位對應的頁,如今經過 「目錄」 就能夠很快地定位到對應的頁上了!(二分查找,時間複雜度近似爲O(logn))
其實底層結構就是B+樹,B+樹做爲樹的一種實現,可以讓咱們很快地查找出對應的記錄。
如下內容整理自:《Java工程師修煉之道》
MySQL中的索引能夠以必定順序引用多列,這種索引叫做聯合索引。如User表的name和city加聯合索引就是(name,city),而最左前綴原則指的是,若是查詢的時候查詢條件精確匹配索引的左邊連續一列或幾列,則此列就能夠被用到。以下:
select * from user where name=xx and city=xx ; //能夠命中索引 select * from user where name=xx ; // 能夠命中索引 select * from user where city=xx ; // 沒法命中索引
這裏須要注意的是,查詢的時候若是兩個條件都用上了,可是順序不一樣,如 city= xx and name =xx
,那麼如今的查詢引擎會自動優化爲匹配聯合索引的順序,這樣是可以命中索引的。
因爲最左前綴原則,在建立聯合索引時,索引字段的順序須要考慮字段值去重以後的個數,較多的放前面。ORDER BY子句也遵循此規則。
冗餘索引指的是索引的功能相同,可以命中就確定能命中 ,那麼 就是冗餘索引如(name,city )和(name )這兩個索引就是冗餘索引,可以命中後者的查詢確定是可以命中前者的 在大多數狀況下,都應該儘可能擴展已有的索引而不是建立新索引。
MySQLS.7 版本後,能夠經過查詢 sys 庫的 schema_redundant_indexes
表來查看冗餘索引
1.添加PRIMARY KEY(主鍵索引)
ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
2.添加UNIQUE(惟一索引)
ALTER TABLE `table_name` ADD UNIQUE ( `column` )
3.添加INDEX(普通索引)
ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
4.添加FULLTEXT(全文索引)
ALTER TABLE `table_name` ADD FULLTEXT ( `column`)
5.添加多列索引
ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
查看MySQL提供的全部存儲引擎
mysql> show engines;
從上圖咱們能夠查看出 MySQL 當前默認的存儲引擎是InnoDB,而且在5.7版本全部的存儲引擎中只有 InnoDB 是事務性存儲引擎,也就是說只有 InnoDB 支持事務。
查看MySQL當前默認的存儲引擎
咱們也能夠經過下面的命令查看默認的存儲引擎。
mysql> show variables like '%storage_engine%';
查看錶的存儲引擎
show table status like "table_name" ;
MyISAM是MySQL的默認數據庫引擎(5.5版以前)。雖然性能極佳,並且提供了大量的特性,包括全文索引、壓縮、空間函數等,但MyISAM不支持事務和行級鎖,並且最大的缺陷就是崩潰後沒法安全恢復。不過,5.5版本以後,MySQL引入了InnoDB(事務性數據庫引擎),MySQL 5.5版本後默認的存儲引擎爲InnoDB。
大多數時候咱們使用的都是 InnoDB 存儲引擎,可是在某些狀況下使用 MyISAM 也是合適的好比讀密集的狀況下。(若是你不介意 MyISAM 崩潰回覆問題的話)。
二者的對比:
READ COMMITTED
和 REPEATABLE READ
兩個隔離級別下工做;MVCC可使用 樂觀(optimistic)鎖 和 悲觀(pessimistic)鎖來實現;各數據庫中MVCC實現並不統一。推薦閱讀:MySQL-InnoDB-MVCC多版本併發控制 《MySQL高性能》上面有一句話這樣寫到:
不要輕易相信「MyISAM比InnoDB快」之類的經驗之談,這個結論每每不是絕對的。在不少咱們已知場景中,InnoDB的速度均可以讓MyISAM可望不可即,尤爲是用到了聚簇索引,或者須要訪問的數據均可以放入內存的應用。
通常狀況下咱們選擇 InnoDB 都是沒有問題的,可是某事狀況下你並不在意可擴展能力和併發能力,也不須要事務支持,也不在意崩潰後的安全恢復問題的話,選擇MyISAM也是一個不錯的選擇。可是通常狀況下,咱們都是須要考慮到這些問題的。
老是假設最壞的狀況,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖。Java中synchronized
和ReentrantLock
等獨佔鎖就是悲觀鎖思想的實現。
老是假設最好的狀況,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可使用版本號機制和CAS算法實現。樂觀鎖適用於多讀的應用類型,這樣能夠提升吞吐量,像數據庫提供的相似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic
包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。
從上面對兩種鎖的介紹,咱們知道兩種鎖各有優缺點,不可認爲一種好於另外一種,像樂觀鎖適用於寫比較少的狀況下(多讀場景),即衝突真的不多發生的時候,這樣能夠省去了鎖的開銷,加大了系統的整個吞吐量。但若是是多寫的狀況,通常會常常產生衝突,這就會致使上層應用會不斷的進行retry,這樣反卻是下降了性能,因此通常多寫的場景下用悲觀鎖就比較合適。
樂觀鎖通常會使用版本號機制或CAS算法實現。
通常是在數據表中加上一個數據版本號version字段,表示數據被修改的次數,當數據被修改時,version值會加一。當線程A要更新數據值時,在讀取數據的同時也會讀取version值,在提交更新時,若剛纔讀取到的version值爲當前數據庫中的version值相等時才更新,不然重試更新操做,直到更新成功。
舉一個簡單的例子: 假設數據庫中賬戶信息表中有一個 version 字段,當前值爲 1 ;而當前賬戶餘額字段( balance )爲 $100 。
這樣,就避免了操做員 B 用基於 version=1 的舊數據修改的結果覆蓋操做員A 的操做結果的可能。
即compare and swap(比較與交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖的狀況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的狀況下實現變量的同步,因此也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三個操做數
當且僅當 V 的值等於 A時,CAS經過原子方式用新值B來更新V的值,不然不會執行任何操做(比較和替換是一個原子操做)。通常狀況下是一個自旋操做,即不斷的重試。
關於自旋鎖,你們能夠看一下這篇文章,很是不錯:《 面試必備之深刻理解自旋鎖》
ABA 問題是樂觀鎖一個常見的問題
若是一個變量V初次讀取的時候是A值,而且在準備賦值的時候檢查到它仍然是A值,那咱們就能說明它的值沒有被其餘線程修改過了嗎?很明顯是不能的,由於在這段時間它的值可能被改成其餘值,而後又改回A,那CAS操做就會誤認爲它歷來沒有被修改過。這個問題被稱爲CAS操做的 "ABA"問題。
JDK 1.5 之後的 AtomicStampedReference 類
就提供了此種能力,其中的 compareAndSet 方法
就是首先檢查當前引用是否等於預期引用,而且當前標誌是否等於預期標誌,若是所有相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。
自旋CAS(也就是不成功就一直循環執行直到成功)若是長時間不成功,會給CPU帶來很是大的執行開銷。 若是JVM能支持處理器提供的pause指令那麼效率會有必定的提高,pause指令有兩個做用,第一它能夠延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它能夠避免在退出循環的時候因內存順序衝突(memory order violation)而引發CPU流水線被清空(CPU pipeline flush),從而提升CPU的執行效率。
CAS 只對單個共享變量有效,當操做涉及跨多個共享變量時 CAS 無效。可是從 JDK 1.5開始,提供了AtomicReference類
來保證引用對象之間的原子性,你能夠把多個變量放在一個對象裏來進行 CAS 操做.因此咱們可使用鎖或者利用AtomicReference類
把多個共享變量合併成一個共享變量來操做。
MyISAM和InnoDB存儲引擎使用的鎖:
表級鎖和行級鎖對比:
詳細內容能夠參考: Mysql鎖機制簡單瞭解一下
InnoDB存儲引擎的鎖的算法有三種:
相關知識點:
當MySQL單表記錄數過大時,數據庫的CRUD性能會明顯降低,一些常見的優化措施以下:
務必禁止不帶任何限制數據範圍條件的查詢語句。好比:咱們當用戶在查詢訂單歷史的時候,咱們能夠控制在一個月的範圍內;
經典的數據庫拆分方案,主庫負責寫,從庫負責讀;
根據數據庫裏面數據表的相關性進行拆分。 例如,用戶表中既有用戶的登陸信息又有用戶的基本信息,能夠將用戶表拆分紅兩個單獨的表,甚至放到單獨的庫作分庫。
簡單來講垂直拆分是指數據表列的拆分,把一張列比較多的表拆分爲多張表。 以下圖所示,這樣來講你們應該就更容易理解了。
保持數據表結構不變,經過某種策略存儲數據分片。這樣每一片數據分散到不一樣的表或者庫中,達到了分佈式的目的。 水平拆分能夠支撐很是大的數據量。
水平拆分是指數據錶行的拆分,表的行數超過200萬行時,就會變慢,這時能夠把一張的表的數據拆成多張表來存放。舉個例子:咱們能夠將用戶信息表拆分紅多個用戶信息表,這樣就能夠避免單一表數據量過大對性能形成影響。
水平拆分能夠支持很是大的數據量。須要注意的一點是:分表僅僅是解決了單一表數據過大的問題,但因爲表的數據仍是在同一臺機器上,其實對於提高MySQL併發能力沒有什麼意義,因此 水平拆分最好分庫 。
水平拆分可以 支持很是大的數據量存儲,應用端改造也少,但 分片事務難以解決 ,跨節點Join性能較差,邏輯複雜。《Java工程師修煉之道》的做者推薦 儘可能不要對數據進行分片,由於拆分會帶來邏輯、部署、運維的各類複雜度 ,通常的數據表在優化得當的狀況下支撐千萬如下的數據量是沒有太大問題的。若是實在要分片,儘可能選擇客戶端分片架構,這樣能夠減小一次和中間件的網絡I/O。
下面補充一下數據庫分片的兩種常見方案:
詳細內容能夠參考: MySQL大表優化方案
騰訊面試:一條SQL語句執行得很慢的緣由有哪些?---不看後悔系列
若是你們想要實時關注我更新的文章以及分享的乾貨的話,能夠關注個人公衆號。
《Java面試突擊》: 由本文檔衍生的專爲面試而生的《Java面試突擊》V2.0 PDF 版本公衆號後臺回覆 "Java面試突擊" 便可免費領取!
Java工程師必備學習資源: 一些Java工程師經常使用學習資源公衆號後臺回覆關鍵字 「1」 便可免費無套路獲取。