我說MySQL里每張表不要超過100w數(shù)據(jù),面試官讓我回去等通知?
1、面試題
- 事務(wù)的幾個特點是什么?
- 數(shù)據(jù)庫事務(wù)有哪些隔離級別?
- MySQL的默認隔離級別?
2、面試官心里分析
用mysql開發(fā)的三個基本面:存儲引擎、索引,然后就是事務(wù),你必須得用事務(wù)。
因為一個業(yè)務(wù)系統(tǒng)里,肯定要加事務(wù)保證一堆關(guān)聯(lián)操作,要么一起成功要么一起失敗,對不對?所以這是聊數(shù)據(jù)庫必問的一個問題
最最最基本的用mysql來開發(fā),就3點:存儲引擎(了解),索引(能建索引,寫的SQL都用上索引),事務(wù)(了解事務(wù)的隔離級別,基于spring的事務(wù)支持在代碼里加事務(wù))
存儲引擎 -> innodb,索引,基本按照你的SQL的需求都建了索引(可能漏了部分索引忘了建),事務(wù)(@Transactional注解,對service層統(tǒng)一加了事務(wù))
3、面試題剖析
3.1 事務(wù)的ACID
這個先說一下ACID,必須得知道:
(1)Atomic:原子性,就是一堆SQL,要么一起成功,要么都別執(zhí)行,不允許某個SQL成功了,某個SQL失敗了,這就是扯淡,不是原子性。
(2)Consistency:一致性,這個是針對數(shù)據(jù)一致性來說的,就是一組SQL執(zhí)行之前,數(shù)據(jù)必須是準(zhǔn)確的,執(zhí)行之后,數(shù)據(jù)也必須是準(zhǔn)確的。別搞了半天,執(zhí)行完了SQL,結(jié)果SQL對應(yīng)的數(shù)據(jù)修改沒給你執(zhí)行,那不是坑爹么。
(3)Isolation:隔離性,這個就是說多個事務(wù)在跑的時候不能互相干擾,別事務(wù)A操作個數(shù)據(jù),弄到一半兒還沒弄好呢,結(jié)果事務(wù)B來改了這個數(shù)據(jù),導(dǎo)致事務(wù)A的操作出錯了,那不就搞笑了。
(4)Durability:持久性,事務(wù)成功了,就必須永久對數(shù)據(jù)的修改是有效的,別過了一會兒數(shù)據(jù)自己沒了,不見了,那就好玩兒了。
3.2 事務(wù)隔離級別
總之,面試問你事務(wù),先聊一下ACID,然后聊聊隔離級別
(1)讀未提交,Read Uncommitted:這個很坑爹,就是說某個事務(wù)還沒提交的時候,修改的數(shù)據(jù),就讓別的事務(wù)給讀到了,這就惡心了,很容易導(dǎo)致出錯的。這個也叫做臟讀。
(2)讀已提交,Read Committed(不可重復(fù)讀):這個比上面那個稍微好一點,但是一樣比較尷尬
就是說事務(wù)A在跑的時候, 先查詢了一個數(shù)據(jù)是值1,然后過了段時間,事務(wù)B把那個數(shù)據(jù)給修改了一下還提交了,此時事務(wù)A再次查詢這個數(shù)據(jù)就成了值2了,這是讀了人家事務(wù)提交的數(shù)據(jù)啊,所以是讀已提交。
這個也叫做不可重復(fù)讀,就是所謂的一個事務(wù)內(nèi)對一個數(shù)據(jù)兩次讀,可能會讀到不一樣的值。如圖:
(3)可重復(fù)讀,Read Repeatable:這個比上面那個再好點兒,就是說事務(wù)A在執(zhí)行過程中,對某個數(shù)據(jù)的值,無論讀多少次都是值1;哪怕這個過程中事務(wù)B修改了數(shù)據(jù)的值還提交了,但是事務(wù)A讀到的還是自己事務(wù)開始時這個數(shù)據(jù)的值。如圖:
(4)幻讀:不可重復(fù)讀和可重復(fù)讀都是針對兩個事務(wù)同時對某條數(shù)據(jù)在修改,但是幻讀針對的是插入
比如某個事務(wù)把所有行的某個字段都修改為了2,結(jié)果另外一個事務(wù)插入了一條數(shù)據(jù),那個字段的值是1,然后就尷尬了。第一個事務(wù)會突然發(fā)現(xiàn)多出來一條數(shù)據(jù),那個數(shù)據(jù)的字段是1。
那么幻讀會帶來啥問題呢?因為在此隔離級別下,例如:事務(wù)1要插入一條數(shù)據(jù),我先查詢一下有沒有相同的數(shù)據(jù),但是這時事務(wù)2添加了這條數(shù)據(jù),這就會導(dǎo)致事務(wù)1插入失敗,并且它就算再一次查詢,也無法查詢到與其插入相沖突的數(shù)據(jù),同時自身死活都插入不了,這就不是尷尬,而是囧了。
(5)串行化:如果要解決幻讀,就需要使用串行化級別的隔離級別,所有事務(wù)都串行起來,不允許多個事務(wù)并行操作。如圖:
(6)MySQL的默認隔離級別是Read Repeatable,就是可重復(fù)讀,就是說每個事務(wù)都會開啟一個自己要操作的某個數(shù)據(jù)的快照,事務(wù)期間,讀到的都是這個數(shù)據(jù)的快照罷了,對一個數(shù)據(jù)的多次讀都是一樣的。
接下來我們聊下MySQL是如何實現(xiàn)Read Repeatable的吧,因為一般我們都不修改這個隔離級別,但是你得清楚是怎么回事兒,MySQL是通過MVCC機制來實現(xiàn)的,就是多版本并發(fā)控制,multi-version concurrency control。
當(dāng)我們使用innodb存儲引擎,會在每行數(shù)據(jù)的最后加兩個隱藏列,一個保存行的創(chuàng)建時間,一個保存行的刪除時間,但是這兒存放的不是時間,而是事務(wù)id,事務(wù)id是mysql自己維護的自增的,全局唯一。
事務(wù)id,在mysql內(nèi)部是全局唯一遞增的,事務(wù)id=1,事務(wù)id=2,事務(wù)id=3
事務(wù)id=121的事務(wù),查詢id=1的這一行的時候,一定會找到創(chuàng)建事務(wù)id <= 當(dāng)前事務(wù)id的那一行
select * from table where id=1,就可以查到上面那一行
事務(wù)id=122的事務(wù),將id=1的這一行給刪除了,此時就會將id=1的行的刪除事務(wù)id設(shè)置成122
事務(wù)id=121的事務(wù),再次查詢id=1的那一行,能查到嗎?
能查到,要求創(chuàng)建事務(wù)id <= 當(dāng)前事務(wù)id,當(dāng)前事務(wù)id < 刪除事務(wù)id
事務(wù)id=121的事務(wù),查詢id=2的那一行,查到name=李四
事務(wù)id=122的事務(wù),將id=2的那一行的name修改成name=小李四
事務(wù)id=121的事務(wù),查詢id=2的那一行,答案是:李四,創(chuàng)建事務(wù)id <= 當(dāng)前事務(wù)id,當(dāng)前事務(wù)id < 刪除事務(wù)id
在一個事務(wù)內(nèi)查詢的時候,mysql只會查詢創(chuàng)建時間的事務(wù)id小于等于當(dāng)前事務(wù)id的行,這樣可以確保這個行是在當(dāng)前事務(wù)中創(chuàng)建,或者是之前創(chuàng)建的;
同時一個行的刪除時間的事務(wù)id要么沒有定義(就是沒刪除),要么是必當(dāng)前事務(wù)id大(在事務(wù)開啟之后才被刪除);滿足這兩個條件的數(shù)據(jù)都會被查出來。
那么如果某個事務(wù)執(zhí)行期間,別的事務(wù)更新了一條數(shù)據(jù)呢?這個很關(guān)鍵的一個實現(xiàn),其實就是在innodb中,是插入了一行記錄,然后將新插入的記錄的創(chuàng)建時間設(shè)置為新的事務(wù)的id,同時將這條記錄之前的那個版本的刪除時間設(shè)置為新的事務(wù)的id。
現(xiàn)在get到這個點了吧?這樣的話,你的這個事務(wù)其實對某行記錄的查詢,始終都是查找的之前的那個快照,因為之前的那個快照的創(chuàng)建時間小于等于自己事務(wù)id,然后刪除時間的事務(wù)id比自己事務(wù)id大,所以這個事務(wù)運行期間,會一直讀取到這條數(shù)據(jù)的同一個版本。