據說你想進大廠?小心這13個MySQL送命題!

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

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

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

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會選擇一個惟一的非空索引代替,若是沒有的話則會隱式定義一個主鍵做爲聚簇索引。sql

這是主鍵聚簇索引存儲的結構,那麼非聚簇索引的結構是什麼樣子呢?非聚簇索引(二級索引)保存的是主鍵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. 那主從的延遲怎麼解決呢?

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

—————END—————

喜歡本文的朋友,歡迎關注公衆號 程序員小灰,收看更多精彩內容

點個[在看],是對小灰最大的支持!
相關文章
相關標籤/搜索