面試必刷:最有用的Mysql面試題,面試了無數公司總結的

想進大廠,mysql不會那可不行,來接受mysql面試挑戰吧,看看你能堅持到哪裏?

1. 能說下myisam 和 innodb的區別嗎?

myisam引擎是5.1版本之前的默認引擎,支持全文檢索、壓縮、空間函數等,但是不支持事務和行級鎖,所以一般用於有大量查詢少量插入的場景來使用,而且myisam不支持外鍵,並且索引和數據是分開存儲的。

innodb是基於聚簇索引建立的,和myisam相反它支持事務、外鍵,並且通過MVCC來支持高併發,索引和數據存儲在一起。

2. 說下mysql的索引有哪些吧,聚簇和非聚簇索引又是什麼?

索引按照數據結構來說主要包含B+樹和Hash索引。

假設我們有張表,結構如下:

create table user(
    id int(11) not null,
  age int(11) not null,
  primary key(id),
  key(age)
);

B+樹是左小右大的順序存儲結構,節點只包含id索引列,而葉子節點包含索引列和數據,這種數據和索引在一起存儲的索引方式叫做聚簇索引,一張表只能有一個聚簇索引。假設沒有定義主鍵,InnoDB會選擇一個唯一的非空索引代替,如果沒有的話則會隱式定義一個主鍵作爲聚簇索引。

這是主鍵聚簇索引存儲的結構,那麼非聚簇索引的結構是什麼樣子呢?非聚簇索引(二級索引)保存的是主鍵id值,這一點和myisam保存的是數據地址是不同的。


最終,我們一張圖看看InnoDB和Myisam聚簇和非聚簇索引的區別

3. 那你知道什麼是覆蓋索引和回表嗎?

覆蓋索引指的是在一次查詢中,如果一個索引包含或者說覆蓋所有需要查詢的字段的值,我們就稱之爲覆蓋索引,而不再需要回表查詢。

而要確定一個查詢是否是覆蓋索引,我們只需要explain sql語句看Extra的結果是否是“Using index”即可。

以上面的user表來舉例,我們再增加一個name字段,然後做一些查詢試試。

explain select * from user where age=1; //查詢的name無法從索引數據獲取
explain select id,age from user where age=1; //可以直接從索引獲取

4. 鎖的類型有哪些呢

mysql鎖分爲共享鎖排他鎖,也叫做讀鎖和寫鎖。

讀鎖是共享的,可以通過lock in share mode實現,這時候只能讀不能寫。

寫鎖是排他的,它會阻塞其他的寫鎖和讀鎖。從顆粒度來區分,可以分爲表鎖行鎖兩種。

表鎖會鎖定整張表並且阻塞其他用戶對該表的所有讀寫操作,比如alter修改表結構的時候會鎖表。

行鎖又可以分爲樂觀鎖悲觀鎖,悲觀鎖可以通過for update實現,樂觀鎖則通過版本號實現。

5. 你能說下事務的基本特性和隔離級別嗎?

事務基本特性ACID分別是:

原子性指的是一個事務中的操作要麼全部成功,要麼全部失敗。

一致性指的是數據庫總是從一個一致性的狀態轉換到另外一個一致性的狀態。比如A轉賬給B100塊錢,假設中間sql執行過程中系統崩潰A也不會損失100塊,因爲事務沒有提交,修改也就不會保存到數據庫。

隔離性指的是一個事務的修改在最終提交前,對其他事務是不可見的。

持久性指的是一旦事務提交,所做的修改就會永久保存到數據庫中。

而隔離性有4個隔離級別,分別是:

read uncommit 讀未提交,可能會讀到其他事務未提交的數據,也叫做髒讀。

用戶本來應該讀取到id=1的用戶age應該是10,結果讀取到了其他事務還沒有提交的事務,結果讀取結果age=20,這就是髒讀。

read commit 讀已提交,兩次讀取結果不一致,叫做不可重複讀。

不可重複讀解決了髒讀的問題,他只會讀取已經提交的事務。

用戶開啓事務讀取id=1用戶,查詢到age=10,再次讀取發現結果=20,在同一個事務裏同一個查詢讀取到不同的結果叫做不可重複讀。

repeatable read 可重複復讀,這是mysql的默認級別,就是每次讀取結果都一樣,但是有可能產生幻讀。

serializable 串行,一般是不會使用的,他會給每一行讀取的數據加鎖,會導致大量超時和鎖競爭的問題。

6. 那ACID靠什麼保證的呢?

A原子性由undo log日誌保證,它記錄了需要回滾的日誌信息,事務回滾時撤銷已經執行成功的sql

C一致性一般由代碼層面來保證

I隔離性由MVCC來保證

D持久性由內存+redo log來保證,mysql修改數據同時在內存和redo log記錄這次操作,事務提交的時候通過redo log刷盤,宕機的時候可以從redo log恢復

7. 那你說說什麼是幻讀,什麼是MVCC?

要說幻讀,首先要了解MVCC,MVCC叫做多版本併發控制,實際上就是保存了數據在某個時間節點的快照。

我們每行數實際上隱藏了兩列,創建時間版本號,過期(刪除)時間版本號,每開始一個新的事務,版本號都會自動遞增。

還是拿上面的user表舉例子,假設我們插入兩條數據,他們實際上應該長這樣。

id name create_version delete_version
1 張三 1
2 李四 2

這時候假設小明去執行查詢,此時current_version=3

select * from user where id<=3;
複製代碼

同時,小紅在這時候開啓事務去修改id=1的記錄,current_version=4

update user set name='張三三' where id=1;
複製代碼

執行成功後的結果是這樣的

id name create_version delete_version
1 張三 1
2 李四 2
1 張三三 4

如果這時候還有小黑在刪除id=2的數據,current_version=5,執行後結果是這樣的。

id name create_version delete_version
1 張三 1
2 李四 2 5
1 張三三 4

由於MVCC的原理是查找創建版本小於或等於當前事務版本,刪除版本爲空或者大於當前事務版本,小明的真實的查詢應該是這樣

select * from user where id<=3 and create_version<=3 and (delete_version>3 or delete_version is null);
複製代碼

所以小明最後查詢到的id=1的名字還是'張三',並且id=2的記錄也能查詢到。這樣做是爲了保證事務讀取的數據是在事務開始前就已經存在的,要麼是事務自己插入或者修改的

明白MVCC原理,我們來說什麼是幻讀就簡單多了。舉一個常見的場景,用戶註冊時,我們先查詢用戶名是否存在,不存在就插入,假定用戶名是唯一索引。

  1. 小明開啓事務current_version=6查詢名字爲'王五'的記錄,發現不存在。

  2. 小紅開啓事務current_version=7插入一條數據,結果是這樣:

id Name create_version delete_version
1 張三 1
2 李四 2
3 王五 7
  1. 小明執行插入名字'王五'的記錄,發現唯一索引衝突,無法插入,這就是幻讀。

8. 那你知道什麼是間隙鎖嗎?

間隙鎖是可重複讀級別下才會有的鎖,結合MVCC和間隙鎖可以解決幻讀的問題。我們還是以user舉例,假設現在user表有幾條記錄

id Age
1 10
2 20
3 30

當我們執行:

begin;
select * from user where age=20 for update;

begin;
insert into user(age) values(10); #成功
insert into user(age) values(11); #失敗
insert into user(age) values(20); #失敗
insert into user(age) values(21); #失敗
insert into user(age) values(30); #失敗

只有10可以插入成功,那麼因爲表的間隙mysql自動幫我們生成了區間(左開右閉)

(negative infinity,10],(10,20],(20,30],(30,positive infinity)

由於20存在記錄,所以(10,20],(20,30]區間都被鎖定了無法插入、刪除。

如果查詢21呢?就會根據21定位到(20,30)的區間(都是開區間)。

需要注意的是唯一索引是不會有間隙索引的。

9. 你們數據量級多大?分庫分表怎麼做的?

首先分庫分表分爲垂直和水平兩個方式,一般來說我們拆分的順序是先垂直後水平。

垂直分庫

基於現在微服務拆分來說,都是已經做到了垂直分庫了

垂直分表

如果表字段比較多,將不常用的、數據較大的等等做拆分

水平分表

首先根據業務場景來決定使用什麼字段作爲分表字段(sharding_key),比如我們現在日訂單1000萬,我們大部分的場景來源於C端,我們可以用user_id作爲sharding_key,數據查詢支持到最近3個月的訂單,超過3個月的做歸檔處理,那麼3個月的數據量就是9億,可以分1024張表,那麼每張表的數據大概就在100萬左右。

比如用戶id爲100,那我們都經過hash(100),然後對1024取模,就可以落到對應的表上了。

10. 那分表後的ID怎麼保證唯一性的呢?

因爲我們主鍵默認都是自增的,那麼分表之後的主鍵在不同表就肯定會有衝突了。有幾個辦法考慮:

  1. 設定步長,比如1-1024張表我們分別設定1-1024的基礎步長,這樣主鍵落到不同的表就不會衝突了。
  2. 分佈式ID,自己實現一套分佈式ID生成算法或者使用開源的比如雪花算法這種
  3. 分表後不使用主鍵作爲查詢依據,而是每張表單獨新增一個字段作爲唯一主鍵使用,比如訂單表訂單號是唯一的,不管最終落在哪張表都基於訂單號作爲查詢依據,更新也一樣。

11. 分表後非sharding_key的查詢怎麼處理呢?

  1. 可以做一個mapping表,比如這時候商家要查詢訂單列表怎麼辦呢?不帶user_id查詢的話你總不能掃全表吧?所以我們可以做一個映射關係表,保存商家和用戶的關係,查詢的時候先通過商家查詢到用戶列表,再通過user_id去查詢。
  2. 打寬表,一般而言,商戶端對數據實時性要求並不是很高,比如查詢訂單列表,可以把訂單表同步到離線(實時)數倉,再基於數倉去做成一張寬表,再基於其他如es提供查詢服務。
  3. 數據量不是很大的話,比如後臺的一些查詢之類的,也可以通過多線程掃表,然後再聚合結果的方式來做。或者異步的形式也是可以的。
List<Callable<List<User>>> taskList = Lists.newArrayList();
for (int shardingIndex = 0; shardingIndex < 1024; shardingIndex++) {
    taskList.add(() -> (userMapper.getProcessingAccountList(shardingIndex)));
}
List<ThirdAccountInfo> list = null;
try {
    list = taskExecutor.executeTask(taskList);
} catch (Exception e) {
    //do something
}

public class TaskExecutor {
    public <T> List<T> executeTask(Collection<? extends Callable<T>> tasks) throws Exception {
        List<T> result = Lists.newArrayList();
        List<Future<T>> futures = ExecutorUtil.invokeAll(tasks);
        for (Future<T> future : futures) {
            result.add(future.get());
        }
        return result;
    }
}

12. 說說mysql主從同步怎麼做的吧?

首先先了解mysql主從同步的原理

  1. master提交完事務後,寫入binlog
  2. slave連接到master,獲取binlog
  3. master創建dump線程,推送binglog到slave
  4. slave啓動一個IO線程讀取同步過來的master的binlog,記錄到relay log中繼日誌中
  5. slave再開啓一個sql線程讀取relay log事件並在slave執行,完成同步
  6. slave記錄自己的binglog

由於mysql默認的複製方式是異步的,主庫把日誌發送給從庫後不關心從庫是否已經處理,這樣會產生一個問題就是假設主庫掛了,從庫處理失敗了,這時候從庫升爲主庫後,日誌就丟失了。由此產生兩個概念。

全同步複製

主庫寫入binlog後強制同步日誌到從庫,所有的從庫都執行完成後才返回給客戶端,但是很顯然這個方式的話性能會受到嚴重影響。

半同步複製

和全同步不同的是,半同步複製的邏輯是這樣,從庫寫入日誌成功後返回ACK確認給主庫,主庫收到至少一個從庫的確認就認爲寫操作完成。

13. 那主從的延遲怎麼解決呢?

這個問題貌似真的是個無解的問題,只能是說自己來判斷了,需要走主庫的強制走主庫查詢。

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