MyISAM和InnoDB
2.1 兩者的對比
count運算上的區(qū)別: 因為MyISAM緩存有表meta-data(行數(shù)等),因此在做COUNT(*)時對于一個結(jié)構(gòu)很好的查詢是不需要消耗多少資源的。而對于InnoDB來說,則沒有這種緩存。
是否支持事務(wù)和崩潰后的安全恢復(fù): MyISAM 強調(diào)的是性能,每次查詢具有原子性,其執(zhí)行數(shù)度比InnoDB類型更快,但是不提供事務(wù)支持。但是InnoDB 提供事務(wù)支持事務(wù),外部鍵等高級數(shù)據(jù)庫功能。 具有事務(wù)(commit)、回滾(rollback)和崩潰修復(fù)能力(crash recovery capabilities)的事務(wù)安全(transaction-safe (ACID compliant))型表。
3)是否支持外鍵: MyISAM不支持,而InnoDB支持。
2.2 關(guān)于兩者的總結(jié)
MyISAM更適合讀密集的表,而InnoDB更適合寫密集的的表。 在數(shù)據(jù)庫做主從分離的情況下,經(jīng)常選擇MyISAM作為主庫的存儲引擎。
一般來說,如果需要事務(wù)支持,并且有較高的并發(fā)讀取頻率(MyISAM的表鎖的粒度太大,所以當該表寫并發(fā)量較高時,要等待的查詢就會很多了),InnoDB是不錯的選擇。如果你的數(shù)據(jù)量很大(MyISAM支持壓縮特性可以減少磁盤的空間占用),而且不需要支持事務(wù)時,MyISAM是最好的選擇。
索引
為什么要使用索引?
1.通過創(chuàng)建唯一性索引,可以保證數(shù)據(jù)庫表中每一行數(shù)據(jù)的唯一性。
2.可以大大加快數(shù)據(jù)的檢索速度(大大減少的檢索的數(shù)據(jù)庫),這也是創(chuàng)建索引的最主要的原因
3.幫助服務(wù)器避免排序和臨時表
4.將隨機IO變?yōu)轫樞騃O
5.可以加速表和表之間的廉連接,特別是在實現(xiàn)數(shù)據(jù)的參考完整性方面特別有意義
索引這么多優(yōu)點,為什么不對表中的每一個列創(chuàng)建一個索引呢?
1.當對表中的數(shù)據(jù)進行增加,刪除和修改額時候,索引也要動態(tài)的維護,這樣就降低了數(shù)據(jù)的維護速度
2.索引需要占物理空間,除了數(shù)據(jù)庫占數(shù)據(jù)空間之外,每一個索引還要占一定的物理空間,如果要建立聚簇索引,那么需要的空間就會更大
3.創(chuàng)建索引和維護索引要耗費時間,這種時間隨著數(shù)據(jù)量的增加而增加
索引是如何提高查詢速度的?
將無序的數(shù)據(jù)變成相對有序的數(shù)據(jù)
說一下使用索引的注意事項?
1.避免where子句中對字段施加函數(shù),這會造成無法命中索引
2.在使用InnoDB時使用與業(yè)務(wù)無關(guān)的自增主鍵作為主鍵,即使用邏輯主鍵而不要使用業(yè)務(wù)主鍵
3.將打算加索引列的列設(shè)置為NOT NULL ,否則將導(dǎo)致引擎放棄使用索引而全表掃描
4.刪除長期未使用的索引,不用的索引的存在會造成不必要的性能耗損MYSQL5.7可以通過查詢sys庫的chema_unuserd_indexes視圖來查詢哪些索引從未被使用
5.在使用limit offset查詢緩慢時,可以借助索引來提高性能
mysql索引主要使用的兩種數(shù)據(jù)結(jié)構(gòu)?
哈希索引:對于哈希索引來說,底層的數(shù)據(jù)結(jié)構(gòu)就是哈希表,因此在絕大多數(shù)需求為單挑記錄查詢的時候,可以選擇哈希索引,查詢性能最快,其余帶部分場景,建議選擇BTree
BTree:mysql的BTree索引使用的是B樹種B+Tree。但對于主要的兩種存儲引擎(MyISAM和InnoDB)的實現(xiàn)方式是不同的。
什么是覆蓋索引?
如果一個索引包含(或者說覆蓋)所有需要查詢的字段的值,我們就稱之為覆蓋索引。我們知道在InnoDB存儲引擎中,如果不是主鍵索引,葉子節(jié)點存儲的是主鍵+列值。最終還是要“回表”,也就是通過主鍵再查找一次,這樣就會比較慢,覆蓋索引就是把要查詢出的列和索引是對應(yīng)的,不做回表操作
樂觀鎖與悲觀鎖
何謂樂觀鎖與悲觀鎖
樂觀鎖對應(yīng)于生活中樂觀的人總是想著事情往好的方向發(fā)展,悲觀鎖對應(yīng)于生活中悲觀的人總是想著事情往壞的方向發(fā)展。這兩種人各有優(yōu)缺點,不能不以場景而定說意中人好于另一種人。
悲觀鎖
總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時候都認為別人會修改,所以每次在拿數(shù)據(jù)的時候都會上鎖。這樣別人想拿這個數(shù)據(jù)就會阻塞直到它拿到鎖(共享資源每次一個線程使用,其他線程阻塞,用完后再把資源轉(zhuǎn)讓給其他線程)。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖,讀鎖,寫鎖等都是在操作之前先加上鎖。Java中synchronized和ReentrantLock等獨占鎖就是悲觀鎖思想的實現(xiàn)。
樂觀鎖
總是假設(shè)最好的情況,每次去拿數(shù)據(jù)的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),可以使用版本號機制和CAS算法實現(xiàn)。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,想數(shù)據(jù)庫提供的類似于write_condition機制,其實都是提供的 樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現(xiàn)方式CAS實現(xiàn)的。
兩種鎖的使用場景
從上面對兩種鎖的介紹,我們知道兩種鎖各有優(yōu)缺點,不可認為一種好于另一種,像樂觀鎖,即沖突真的很少發(fā)生的時候,我們可以省去了鎖的開銷,加大了系統(tǒng)的整個吞吐量。但是如果是多寫的情況,一般會經(jīng)常產(chǎn)生沖突,這就會導(dǎo)致上層應(yīng)用會不斷的進行retry,這樣反倒是降低了性能,悲觀鎖一般多寫的場景下就比較適用。
樂觀鎖常見的兩種實現(xiàn)方式
1.版本號機制
一般是在數(shù)據(jù)表中加上一個數(shù)據(jù)版本號version字段,表示數(shù)據(jù)被修改的次數(shù),當數(shù)據(jù)修改時,version值會加一。當線程A要更新數(shù)據(jù)值時,在讀取數(shù)據(jù)的同時也會讀取version值,在提交更新時,若剛才讀取到的 version值為當前數(shù)據(jù)庫中的version值相等時才更新,否則重試更新操作,直到更新成功。
舉例:
假設(shè)數(shù)據(jù)庫中賬號信息表中有一個versin字段,當前值為1,而當前賬戶余額字段balance為100
a.操作員A此時將其讀出version=1,并從其賬號余額中扣除50元(100-50)
b.操作員A操作過程中,操作員B也讀入此用戶信息(version=1),并從其賬號中扣除20(100-20)
c.操作員A完成了修改工作,將數(shù)據(jù)版本號加1(versison=2),連同賬號扣除后余額(balance=50)一起提交至數(shù)據(jù)庫更新,此時由于提交數(shù)據(jù)庫版本大于數(shù)據(jù)庫記錄當前版本,數(shù)據(jù)被更新,數(shù)據(jù)庫記錄version=2
d.操作員B完成了操作,也將版本號加1(version=2)試圖向數(shù)據(jù)庫提交數(shù)據(jù)(balance=80)但此時比對數(shù)據(jù)庫記錄版本時發(fā)現(xiàn),操作員B提交的數(shù)據(jù)版本也為2,數(shù)據(jù)庫記錄版本也為2,不滿足“提交版本必須大于當前版本才能執(zhí)行更新”的樂觀鎖策略,因此操作員B的提交被駁回。
這樣就避免了操作員B基于version=1的舊數(shù)據(jù)修改的結(jié)果覆蓋操作員A的操作結(jié)果的可能。
2.CAS算法
即“compare and swap(比較與交換)”是一種有名的無鎖算法,無鎖編程,即不使用鎖的情況下實現(xiàn)多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現(xiàn)變量的同步,所以也叫非阻塞同步(Non-blocking synchronized)CAS算法涉及到三個操作數(shù)
-需要讀寫的內(nèi)存值V
-進行比較的值A(chǔ)
-擬寫入的新值B
當且僅當V的值等于A時,CAS通過原子方式用新值B來更新V的值,否則不會執(zhí)行任何操作(比較好餓替換是一個原子操作)。一般情況下是一個自旋操作,即不斷的重試。
關(guān)于自旋鎖,大家可以看一下這篇文章,非常不錯:[面試必備之深入理解自旋鎖》](https://blog.csdn.net/qq_34337272/article/details
樂觀鎖的缺點
1.ABA問題
如果一個變量V初次讀取到的時候是A值,并且在準備賦值的時候檢查到它仍然是A值,那我們就能說明它的值沒有被其他線程修改了嗎?很明顯不能的,因為在這段時間它的值可能被修改為其他值,然后又改回A,那么CAS操作就會誤以為它沒被修改過,這個問題被稱為ABA問題。
JDK1.5以后的AtomicStampedReference類就提供了此種你能力其中compareAndSet方法就是首先檢查當前引用是否等于預(yù)期引用,并且當前標志是否等于預(yù)期標志,如果全部相等,則以原子方式將該引用和該標志的值設(shè)置為給定的值。
2.循環(huán)時間長開銷大
自旋CAS(也就是不成功就一直循環(huán)執(zhí)行直到成功)如果長時間不成功,會給CPU帶來非常大的執(zhí)行開銷,如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個作用,第一它看運氣延遲流水線執(zhí)行指令(de-pipeline),使CPU不會消耗過多的執(zhí)行資源,延遲時間取決于具體實現(xiàn)的版本,在一些處理器上延遲時間是零,第二他可以避免在退出循環(huán)的時候因內(nèi)存順序沖突(momory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執(zhí)行效率。
3.只能保證一個共享變量的原子操作
CAS只針對單個共享變量有效,當操作涉及跨多個共享變量時CAS無效,但是從JDK1.5開始,提供了AtomicReference來保證引用對象之間的原子性,你可以把多個變量放在一個對象來進行CAS操作,所以我們可以使用鎖或者利用AtomicReference把多個共享變量合并成一個共享變量來操作。
CAS和synchronized的使用場景
簡單來說CAS適用于寫比較少的情況下(多讀場景,沖突一般較少),synchronized適用于寫比較多的情況下(多寫場景,沖突一般比較多)
對于資源競爭少(線程沖突較輕)的情況,使用synchronized同步鎖進行線程阻塞和喚醒切換以及用戶態(tài)內(nèi)核態(tài)間的切換操作額外浪費消耗cpu資源,而CAS基于硬件實現(xiàn),不需要進入內(nèi)核,不需要切換線程,操作自旋幾率較少,因此獲得更高的性能
對于資源競爭嚴重(線程沖突嚴重)的情況,CAS自旋的概率比較大,從而浪費更多的cpu資源,效率低于synchronized。
補充:
Java并發(fā)編程這個領(lǐng)域synchronized關(guān)鍵字一直都是元老級的角色 ,很久之前很多人都會稱它為重量級鎖,但是在JavaSE1.6以后進行了主要包括為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖以及其他各種優(yōu)化之后變得在某些情況下并不是那么重了,synchronized的底層實現(xiàn)主要依靠Lock-Free的隊列,基本思路是自旋后阻塞,競爭切換后繼續(xù)競爭鎖,稍微犧牲了公平性,但是獲得了更高的吞吐量,在線程沖突較少的情況下,可以獲得和CAS類似的性能,而線程沖突嚴重的情況下,性能遠高于CAS。