什麼是MySQL?java
MySQL 是一種關係型數據庫,在Java企業級開發中很是經常使用,由於 MySQL 是開源免費的,而且方便擴展。阿里巴巴數據庫系統也大量用到了 MySQL,所以它的穩定性是有保障的。MySQL是開放源代碼的,所以任何人均可以在 GPL(General Public License) 的許可下下載並根據個性化的須要對其進行修改。MySQL的默認端口號是3306。mysql
事務是邏輯上的一組操做,要麼都執行,要麼都不執行。算法
事務最經典也常常被拿出來講例子就是轉帳了。假如小明要給小紅轉帳1000元,這個轉帳會涉及到兩個關鍵操做就是:將小明的餘額減小1000元,將小紅的餘額增長1000元。萬一在這兩個操做之間忽然出現錯誤好比銀行系統崩潰,致使小明餘額減小而小紅的餘額沒有增長,這樣就不對了。事務就是保證這兩個關鍵操做要麼都成功,要麼都要失敗。sql
原子性: 事務是最小的執行單位,不容許分割。事務的原子性確保動做要麼所有完成,要麼徹底不起做用;數據庫
一致性: 執行事務先後,數據保持一致,多個事務對同一個數據讀取的結果是相同的;編程
隔離性: 併發訪問數據庫時,一個用戶的事務不被其餘事務所幹擾,各併發事務之間數據庫是獨立的;安全
持久性: 一個事務被提交以後。它對數據庫中數據的改變是持久的,即便數據庫發生故障也不該該對其有任何影響。網絡
在典型的應用程序中,多個事務併發運行,常常會操做相同的數據來完成各自的任務(多個用戶對統一數據進行操做)。併發雖然是必須的,但可能會致使如下的問題:多線程
髒讀(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)就會發現多了一些本來不存在的記錄,就好像發生了幻覺同樣,因此稱爲幻讀。
不可重複度和幻讀區別:
不可重複讀的重點是修改,幻讀的重點在於新增或者刪除。
例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(可串行化): 最高的隔離級別,徹底服從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 存儲引擎默認使用 REPEATABLE-READ(可重讀)並不會有任何性能損失。
InnoDB 存儲引擎在 分佈式事務 的狀況下通常會用到SERIALIZABLE(可串行化)隔離級別。
如下內容整理自:《數據庫兩大神器【索引和鎖】》
先從 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 崩潰回覆問題的話)。
二者的對比:
是否支持行級鎖 : MyISAM 只有表級鎖(table-level locking),而InnoDB 支持行級鎖(row-level locking)和表級鎖,默認爲行級鎖。
是否支持事務和崩潰後的安全恢復:MyISAM 強調的是性能,每次查詢具備原子性,其執行比InnoDB類型更快,可是不提供事務支持。可是InnoDB 提供事務支持事務,外部鍵等高級數據庫功能。具備事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。
是否支持外鍵: MyISAM不支持,而InnoDB支持。
是否支持MVCC :僅 InnoDB 支持。應對高併發事務, MVCC比單純的加鎖更高效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 兩個隔離級別下工做;MVCC可使用 樂觀(optimistic)鎖 和 悲觀(pessimistic)鎖來實現;各數據庫中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 。
操做員 A 此時將其讀出( version=1 ),並從其賬戶餘額中扣除 $50( $100-$50 )。
在操做員 A 操做的過程當中,操做員B 也讀入此用戶信息( version=1 ),並從其賬戶餘額中扣除 $20 ( $100-$20 )。
操做員 A 完成了修改工做,將數據版本號加一( version=2 ),連同賬戶扣除後餘額( balance=$50 ),提交至數據庫更新,此時因爲提交數據版本大於數據庫記錄當前版本,數據被更新,數據庫記錄 version 更新爲 2 。
操做員 B 完成了操做,也將版本號加一( version=2 )試圖向數據庫提交數據( balance=$80 ),但此時比對數據庫記錄版本時發現,操做員 B 提交的數據版本號爲 2 ,數據庫記錄當前版本也爲 2 ,不知足 「 提交版本必須大於記錄當前版本才能執行更新 「 的樂觀鎖策略,所以,操做員 B 的提交被駁回。
這樣,就避免了操做員 B 用基於 version=1 的舊數據修改的結果覆蓋操做員A 的操做結果的可能。
即compare and swap(比較與交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖的狀況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的狀況下實現變量的同步,因此也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三個操做數
須要讀寫的內存值 V
進行比較的值 A
擬寫入的新值 B
當且僅當 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存儲引擎使用的鎖:
MyISAM 採用表級鎖(table-level locking)。
InnoDB 支持行級鎖(row-level locking)和表級鎖,默認爲行級鎖
表級鎖和行級鎖對比:
表級鎖: Mysql中鎖定 粒度最大 的一種鎖,對當前操做的整張表加鎖,實現簡單,資源消耗也比較少,加鎖快,不會出現死鎖。其鎖定粒度最大,觸發鎖衝突的機率最高,併發度最低,MyISAM和 InnoDB引擎都支持表級鎖。
行級鎖: Mysql中鎖定 粒度最小 的一種鎖,只針對當前操做的行進行加鎖。行級鎖能大大減小數據庫操做的衝突。其加鎖粒度最小,併發度高,但加鎖的開銷也最大,加鎖慢,會出現死鎖。
InnoDB存儲引擎的鎖的算法有三種:
Record lock:單個行記錄上的鎖
Gap lock:間隙鎖,鎖定一個範圍,不包括記錄自己
Next-key lock:record+gap 鎖定一個範圍,包含記錄自己
相關知識點:
innodb對於行的查詢使用next-key lock
Next-locking keying爲了解決Phantom Problem幻讀問題
當查詢的索引含有惟一屬性時,將next-key lock降級爲record key
Gap鎖設計的目的是爲了阻止多個事務將記錄插入到同一範圍內,而這會致使幻讀問題的產生
有兩種方式顯式關閉gap鎖:(除了外鍵約束和惟一性檢查外,其他狀況僅使用record lock) A. 將事務隔離級別設置爲RC B. 將參數innodb_locks_unsafe_for_binlog設置爲1
當MySQL單表記錄數過大時,數據庫的CRUD性能會明顯降低,一些常見的優化措施以下:
務必禁止不帶任何限制數據範圍條件的查詢語句。好比:咱們當用戶在查詢訂單歷史的時候,咱們能夠控制在一個月的範圍內;
經典的數據庫拆分方案,主庫負責寫,從庫負責讀;
根據數據庫裏面數據表的相關性進行拆分。 例如,用戶表中既有用戶的登陸信息又有用戶的基本信息,能夠將用戶表拆分紅兩個單獨的表,甚至放到單獨的庫作分庫。
簡單來講垂直拆分是指數據表列的拆分,把一張列比較多的表拆分爲多張表。 以下圖所示,這樣來講你們應該就更容易理解了。
垂直拆分的優勢: 可使得列數據變小,在查詢時減小讀取的Block數,減小I/O次數。此外,垂直分區能夠簡化表的結構,易於維護。
垂直拆分的缺點: 主鍵會出現冗餘,須要管理冗餘列,並會引發Join操做,能夠經過在應用層進行Join來解決。此外,垂直分區會讓事務變得更加複雜;
保持數據表結構不變,經過某種策略存儲數據分片。這樣每一片數據分散到不一樣的表或者庫中,達到了分佈式的目的。水平拆分能夠支撐很是大的數據量。
水平拆分是指數據錶行的拆分,表的行數超過200萬行時,就會變慢,這時能夠把一張的表的數據拆成多張表來存放。舉個例子:咱們能夠將用戶信息表拆分紅多個用戶信息表,這樣就能夠避免單一表數據量過大對性能形成影響。
水平拆分能夠支持很是大的數據量。須要注意的一點是:分表僅僅是解決了單一表數據過大的問題,但因爲表的數據仍是在同一臺機器上,其實對於提高MySQL併發能力沒有什麼意義,因此 水平拆分最好分庫 。
水平拆分可以 支持很是大的數據量存儲,應用端改造也少,但 分片事務難以解決,跨節點Join性能較差,邏輯複雜。《Java工程師修煉之道》的做者推薦 儘可能不要對數據進行分片,由於拆分會帶來邏輯、部署、運維的各類複雜度 ,通常的數據表在優化得當的狀況下支撐千萬如下的數據量是沒有太大問題的。若是實在要分片,儘可能選擇客戶端分片架構,這樣能夠減小一次和中間件的網絡I/O。
下面補充一下數據庫分片的兩種常見方案:
客戶端代理: 分片邏輯在應用端,封裝在jar包中,經過修改或者封裝JDBC層來實現。 噹噹網的 Sharding-JDBC 、阿里的TDDL是兩種比較經常使用的實現。
中間件代理: 在應用和數據中間加了一個代理層。分片邏輯統一維護在中間件服務中。 咱們如今談的 Mycat 、360的Atlas、網易的DDB等等都是這種架構的實現。
記得點點關注哦,天天會分享Java有關的文章還有福利分享