Java 面試八股文之數據庫篇(二)

前言

image

這是系列文章【 Java 面試八股文】數據庫篇的第二期。

【 Java 面試八股文】系列會陸續更新 Java 面試中的高頻問題,旨在從問題出發,理解 Java 基礎,數據結構與算法,數據庫,常用框架等。該系列前幾期文章可以通過點擊文末給出的鏈接進行查看~

按照慣例——首先要做幾點說明:

  1. 【 Java 面試八股文】中的面試題來源于社區論壇,書籍等資源;感謝使我讀到這些寶貴面經的作者們。
  2. 對于【 Java 面試八股文】中的每個問題,我都會盡可能地寫出我自己認為的“完美解答”。但是畢竟我的身份不是一個“真理持有者”,只是一個秉承著開源分享精神的 “knowledge transmitter” & 菜雞,所以,如果這些答案出現了錯誤,可以留言寫出你認為更好的解答,并指正我。非常感謝您的分享。
  3. 知識在于“融釋貫通”,而非“死記硬背”;現在市面上固然有很多類似于“Java 面試必考 300 題” 這類的文章,但是普遍上都是糟粕,僅講述其果,而不追其源;希望我的【 Java 面試八股文】可以讓你知其然,且知其所以然~

那么,廢話不多說,我們正式開始吧!

往期文章

數據庫篇(二)

1、如何定位并優化慢查詢 sql?


我們從定位到慢查詢 sql,再通過分析并進行優化的順序如下:

  1. 通過開啟慢日志定位到慢查詢的 sql
  2. 使用 explain 工具分析 sql
  3. 修改并優化 sql

接下來我們就依次通過以上三個步驟來看一下,一條慢查詢 sql 是如何被找到,并優化的。

1. 通過開啟慢日志定位到慢查詢 sql

首先,我們進入到客戶端,輸入命令:

show variables like '%query%';

命令返回結果如下:

mysql> show variables like '%query%';
+------------------------------+--------------------------------------+
| Variable_name                | Value                                |
+------------------------------+--------------------------------------+
| binlog_rows_query_log_events | OFF                                  |
| ft_query_expansion_limit     | 20                                   |
| have_query_cache             | YES                                  |
| long_query_time              | 10.000000                            |
| query_alloc_block_size       | 8192                                 |
| query_cache_limit            | 1048576                              |
| query_cache_min_res_unit     | 4096                                 |
| query_cache_size             | 1048576                              |
| query_cache_type             | OFF                                  |
| query_cache_wlock_invalidate | OFF                                  |
| query_prealloc_size          | 8192                                 |
| slow_query_log               | OFF                                  |
| slow_query_log_file          | /var/lib/mysql/23f9bc5132dc-slow.log |
+------------------------------+--------------------------------------+
13 rows in set (0.01 sec)

在這里面,我們需要關注三個變量,分別是:slow_query_logslow_query_log_file 以及 long-query_time

slow_query_log 目前對應的 Value 值為 OFF,代表慢日志并未開啟;slow_query_log_file 是記錄慢 sql 的文件,當一條查詢 sql 的時間超過 long_query_time 時,就會被記錄到慢日志文件中,我們看到 long_query_time 的默認值為 10 s。

首先,我們需要開啟慢日志,使用命令:

set global slow_query_log = on;

并且,通常我們會修改 long_query_time 的值。因為如果一條查詢 sql 的執行時間超過 10 s 才被定義為慢查詢的話,一般是不能被接受的。我們可以按照自己的業務需求,設定相應的值,譬如將其設置為 1 s:

set global long_query_time = 1;

重新連接數據庫后,就可以看到剛剛設置的值已經生效了:

mysql> show variables like '%query%';
+------------------------------+--------------------------------------+
| Variable_name                | Value                                |
+------------------------------+--------------------------------------+
| binlog_rows_query_log_events | OFF                                  |
| ft_query_expansion_limit     | 20                                   |
| have_query_cache             | YES                                  |
| long_query_time              | 1.000000                             |
| query_alloc_block_size       | 8192                                 |
| query_cache_limit            | 1048576                              |
| query_cache_min_res_unit     | 4096                                 |
| query_cache_size             | 1048576                              |
| query_cache_type             | OFF                                  |
| query_cache_wlock_invalidate | OFF                                  |
| query_prealloc_size          | 8192                                 |
| slow_query_log               | ON                                   |
| slow_query_log_file          | /var/lib/mysql/23f9bc5132dc-slow.log |
+------------------------------+--------------------------------------+
13 rows in set (0.01 sec)

除了使用命令進行修改,我們也可以通過修改配置文件(my.cnf)對這些變量進行設置,修改配置文件這種方式會使得這些改動永久保存,不會因為重啟數據庫服務而失效。

這樣,我們就可以通過開啟慢日志定位到所有超時的慢查詢 sql 語句了。

2. 使用 explain 工具分析 sql

explain 是 MySQL 內置的一個命令,可以獲取一條語句的執行計劃。語法為 explain + 要分析的語句。通過 explain 的分析我們可以知道表的讀取順序,數據讀取操作的類型,是否有使用到索引,有無做全表掃描等信息。

目前我有一張學生表:

create table student (
    id int(11) not null auto_increment,
    name varchar(10) default null,
    motto varchar(50) default null,
    primary key(id)
)engine=InnoDB default charset=utf8;

在慢查詢日志文件中,有一條超時的查詢語句:

select name from student order by name desc;

我們可以使用 explain 工具對這條查詢語句進行解析:

explain select name from student order by name desc;

返回結果如下:

+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+
|  1 | SIMPLE      | student | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 102102 |   100.00 | Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+

我來解釋一下其中一些重點的列分別代表什么含義。

id 代表 select 查詢語句的序列號。select_type 表示對應行是簡單查詢還是復雜查詢,譬如,我們這條語句既不包含子查詢也沒有聯合查詢,所以被定義為簡單查詢,顯示的信息為 SIMPLE。table 列的含義很簡單了,表示我們當前解析的這條 sql 語句訪問的是哪一張表。type 列表示 MySQL 查詢數據行的方式,從最優到最差分別為:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。ALL 表示走全表掃描,就意味著 MySQL 需要從頭到尾遍歷整張表去尋找我們所需要的數據行。所以當我們使用 explain 分析出的結果中,type 列顯示的信息為 ALL 時,就需要留意這條查詢語句是否有可以優化的空間。possible_keys 列顯示查詢可能使用哪些索引來查找。key 列顯示 MySQL 實際采用哪個索引來優化對該表的訪問,如果我們沒有使用索引,那么該列的信息則為 NULL。rows 列對應的信息是 MySQL 估計要讀取并檢測的行數。Extra 列展示的是額外信息,Extra 列如果對應的是以下的兩個值則說明我們的語句無法使用索引,效率會受重大影響:第一個是 Using filesort,第二個是 Using temporary。在出現這兩個字段時,往往我們都會考慮對 sql 進行優化。

當 Extra 列為 Using filesort 時,表示 MySQL 會對結果使用一個外部索引排序,而不是從表里按照索引次序讀到相關內容。可能是在內存或者磁盤上進行排序,MySQL 中無法利用索引完成的排序操作稱為 “文件排序”。

而當 Extra 列為 Using temporary 時,表示 MySQL 創建了一張臨時表來處理查詢。Using teporary 常見于排序操作 order by 和分組查詢 group by,當值為 Using temporary 時,sql 查詢一般都是需要進行優化的。

3. 修改并優化 sql

解釋了這些列表示的含義后,我們再回到 explain 解析的結果:

+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+
|  1 | SIMPLE      | student | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 102102 |   100.00 | Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+----------------+

可以看到,我們的 type 列對應的值為 ALL,說明該查詢語句走了全表掃描。

這條 sql 查詢優化的方式很簡單,我們可以為這張表的 name 字段加上一個索引:

alter table student add index idx_name (name);

我們再來執行一遍 explain 解析命令:

+----+-------------+---------+------------+-------+---------------+----------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type  | possible_keys | key      | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+-------+---------------+----------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | index | NULL          | idx_name | 33      | NULL | 102102 |   100.00 | Using index |
+----+-------------+---------+------------+-------+---------------+----------+---------+------+--------+----------+-------------+

可以看到,此時的 type 列變為了 index,表示 MySQL 獲取數據行的方式是掃描索引樹,并且 Extra 列的值也變成了 Using index,表示我們使用了索引的方式來獲取結果。

這樣我們的慢查詢 sql 就得到了優化。

總結

本題的解答中,這個示例非常簡單,目的就是為了給大家提供一個思路。首先,我們需要開啟慢日志定位并獲取慢查詢的 sql 語句,然后我們可以使用 explain 命令來對這條 sql 語句進行分析,看這條查詢 sql 是否使用到索引,是否創建了臨時表等。最后我們通過和業務場景進行結合,就可以分析得到優化 sql 的具體思路。

2、索引創建的越多越好嗎?


當然不是。

索引可以比作是一本書的目錄,當我們想要找到書本中的某些內容時,我們可以先通過目錄快速定位到相應的章節,這便大大提升了我們的查找效率。

不過試想一下,如果我們的這本書只有兩三頁,那就大可不必再為這本書新增一頁紙當作目錄了。所以,對于一些小型表來說,創建索引是浪費存儲空間的。小型表在大部分情況下即便是全表掃描效率也不會太低,所以這種情況下就沒必要創建索引。

對于中型表,大型表來說,正確地創建索引的確會提升查詢效率,但也絕不是越多越好。我們要知道,數據變更需要維護索引,因此更多的索引就意味著更多的維護成本,同時更多的索引也意味著需要更多的存儲空間。

而對于特大型表來說,我們更要謹慎地使用索引,因為使用索引越多維護起來的代價也會越大。實際上,對于特大型表,我們應該考慮的是分庫分表的策略,可以使用一些分庫分表工具,譬如 sharding-sphere,TDDL,Mycat 等,分散并減輕庫與表的壓力。

總結

索引越多越好?答案必然是否定的。面試者應該從索引的使用場景,索引的利弊來回答本問題。

3、請談一下 MySQL 的鎖機制?


鎖機制非常重要,在這個問題下涵蓋了許多經常被考察到的面試題,所以接下來,我會用較長的篇幅來回答這個問題并詳細解釋其中的每個知識點。

數據庫鎖按照不同的分類可以做如下劃分:

  • 按照鎖的粒度劃分,可分為表級鎖,行級鎖,頁級鎖
  • 按照鎖的級別劃分,可分為共享鎖與排它鎖
  • 按照加鎖的方式劃分,可分為自動鎖與顯示鎖
  • 按照使用方式劃分,可分為樂觀鎖,悲觀鎖

我們都知道 MySQL 的存儲引擎是插件式的,不同的存儲引擎有不同的鎖機制,其中我們最常用到的兩個存儲引擎為 MyISAM 與 InnoDB,MyISAM 存儲引擎采用的是表級鎖(table-level-locking),InnoDB 存儲引擎既支持行級鎖(row-level-locking)也支持表級鎖,但是默認的情況下采用的是行級鎖。而我們不常使用到的一種存儲引擎—— BDB 采用的是頁級鎖(page-level-locking),同時 BDB 也支持表級鎖,不過,因為現在我們使用到的主流存儲引擎幾乎都是 MyISAM 與 InnoDB,而且 BDB 現在已經完全被 InnoDB 取代,所以我們對頁級鎖就不再介紹了,對此感興趣的童鞋可以自行了解。

那么先來看一下 MyISAM 存儲引擎采用的鎖機制。

首先,我們要了解共享鎖和排它鎖的概念。為了不給大家帶來太復雜的概念,你可以先這樣認為:共享鎖等價于讀鎖(Read Lock),排它鎖等價于寫鎖(Write Lock)。當我們對一張表進行查詢操作(select)時,MyISAM 會為這張表自動加上一個讀鎖;當我們對一張表進行更新操作(insert,update,delete)時,MyISAM 則在這張表上自動加上一個寫鎖。

共享鎖(讀鎖)與排它鎖(寫鎖)的兼容性如下表所示:

image

啥意思呢?在有多個用戶(線程)對 MyISAM 表進行讀操作時,并不會阻塞其他用戶對同一表的讀請求。所以,我們說讀鎖和讀鎖之間是相互兼容的,而其他的方式則是不兼容的。

舉個例子??:現在我有一張 test 表,指定使用的存儲引擎為 MyISAM :

create table test
(
    id        int(11) not null auto_increment,
    test_no   varchar(20) default null,
    test_desc varchar(50) default null,
    primary key (id),
    key (test_no)
) engine = MyISAM
  default charset = utf8;

假設,有兩個用戶正在對表進行查詢(讀)操作。

用戶 A 輸入了一條查詢 sql :

select * from test where id between 1 and 1000000;

在用戶 A 還沒有拿到結果時,用戶 B 也輸入了一條查詢 sql :

select * from test where id between 500000 and 1000000;

此時,用戶 B 并不會因為用戶 A 還沒有拿到結果而進入到線程阻塞的等待狀態。原因就是,讀鎖和讀鎖是相互兼容的。

如果用戶 A 輸入了一條查詢 sql:

select * from test where id between 1 and 1000000;

在用戶 A 還沒有拿到查詢結果時,用戶 B 輸入了一條更新 sql:

update test set test_no = null where id = 1000000;

而這個時候,用戶 B 輸入的 update 語句會被阻塞,需要等待 test 表的讀鎖釋放后才可以進行操作,原因就在于寫鎖是一種排它鎖,并不會和其他線程共享。

以上的方式均為 MyISAM 自動加鎖完成的,所以這種方式叫做自動鎖。

我們也可以手動加鎖。譬如有這樣的一個場景:

有一個訂單表 orders,其中記錄著各訂單的總金額 total;同時還有一個訂單明細表 order_detail,記錄著各訂單每一類產品的金額小記 subtotal。我們現在有一個需求,就是檢查這兩個表的金額合計是否相同,那么就需要執行這樣兩條 sql 語句:

select sum(total) from orders;
select sum(subtotal) from order_detail;

如果我們不先為這兩個表加鎖,就很有可能出現錯誤。比如第一個查詢語句執行完畢并返回了結果,然后再對 orders 表做一個更新,那么兩次查詢的結果就不一致了。

為了避免這種情況的發生,我們需要在這兩條查詢語句的前后為這兩個表手動加讀鎖,然后再釋放:

lock tables orders read,order_detail read;
select sum(total) from order;
select sum(subtotal) from order_detail;
unlock tables;

對于 MyISAM 存儲引擎,手動加鎖的方式就是:

lock tables T read; # 寫鎖為 write
...
unlock tables;

而鎖的粒度是在整張表上的。

我們再來看一下 InnoDB 存儲引擎的鎖機制。

首先,我們創建一張使用 InnoDB 引擎的測試表 test2:

create table test2
(
    id        int(11) not null auto_increment,
    test_no   varchar(20) default null,
    test_desc varchar(50) default null,
    primary key (id),
    key (test_no)
) engine = InnoDB
  default charset = utf8;

InnoDB 存儲引擎支持事務,并且在默認的情況下是自動提交事務的,它的機制是為同一批事務提交之前加鎖,然后 commit 后再一起釋放。這里面我為了復現 InnoDB 鎖的機制,對當前的幾個 MySQL 客戶端窗口進行了設置:

set autocommit = 0;

我們將 autocommit 設置為 0,這樣就可以關閉事務的自動提交。

現在假設,用戶 A 執行了一條 sql 查詢語句:

select * from test2 where id = 3 lock in share mode ;

InnoDB 默認的鎖是行級鎖,lock in share mode 表示我們對 test2 表中 id = 3 的行加了一個共享鎖,如果要加排它鎖,我們可以使用 for update。現在用戶 A 還沒有執行 commit;用戶 B 執行了一條語句:

update test2 set test_no = null where id = 4;

此時,我們發現,這條語句是可以執行成功的,并沒有發生阻塞:

mysql> update test2 set test_no = null where id = 4;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0

如果用戶 B 執行的語句為:

update test2 set test_no = null where id = 3;

那么,用戶 B 將進入阻塞等待狀態。

這便驗證了 InnoDB 默認加鎖的粒度是行級鎖——不過,有個前提條件,那就是查詢的字段上必須有索引,如果沒有索引 InnoDB 還是會對整個表加鎖。

再舉個例子:

test2 表的數據內容如下:

+----+---------+-----------+
| id | test_no | test_desc |
+----+---------+-----------+
|  1 | NULL    | NULL      |
|  2 | NULL    | NULL      |
|  3 | NULL    | test1     |
|  4 | NULL    | NULL      |
|  5 | NULL    | NULL      |
|  6 | NULL    | NULL      |
|  7 | NULL    | NULL      |
|  8 | NULL    | NULL      |
|  9 | NULL    | test      |
| 10 | NULL    | NULL      |
+----+---------+-----------+

假如用戶 A 執行了一條 sql 語句:

select * from test2 where test_desc = 'test' lock in share mode;

還未 commit,用戶 B 執行了一條 sql 語句:

update test2 set test_desc = null where test_desc = 'test1';

此時,用戶 B 被阻塞。

我們看到,用戶 A 在查詢 id 為 9 的行,而用戶 B 則在更新 id 為 3 的行,如果 InnoDB 走的是行級鎖,那么用戶 A 對第九行加了一個共享鎖,用戶 B 對第三行加了一個排它鎖,理應是不會互相阻塞的,可是用戶 B 仍然被阻塞了。這說明,當我們的 sql 語句操作字段沒有走索引時,InnoDB 還是會在整個表這個粒度上加鎖。所以說,InnoDB 既支持行級鎖,也支持表級鎖。

那么行級鎖一定要比表級鎖好么?

其實不然,行級鎖的并發度雖然比表級鎖要高,但是表級鎖的開銷比較小,加鎖的速度很快;行級鎖的開銷則比較大,并且加鎖的速度慢,最重要的是行級鎖可能會出現死鎖現象

什么是死鎖(Dead Lock)?

死鎖是指兩個或兩個以上的進程或線程在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。

image

如上圖中所示,線程 A 持有鎖 A,線程 B 持有鎖 B。兩個線程處于爭搶狀態,線程 A 等待線程 B 釋放鎖,線程 B 又等待線程 A 釋放它的資源,這樣相互等待就形成了死鎖。

形成死鎖必須滿足四個必要條件:

  1. 互斥條件
  2. 請求與保持條件
  3. 不剝奪條件
  4. 環路等待條件

我們接下來對這些名字逐一進行解釋。

互斥條件是指進程對所分配到的資源進行排它性使用。說白了就是在一段時間內,某個資源每次只能被一個進程所占用。

請求與保持條件是指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放,說人話就是“有了碗里的餅,還嚷嚷著要吃鍋里的湯”。

不剝奪條件是指進程已獲得的資源在未使用完之前,不能被剝奪,只能由自己釋放,說人話就是“自己拿到了就不給別人用,自己用完了才給別人”。

環路等待條件是指當發生死鎖時,必然存在著一個資源的請求環形鏈,若干進程在這個環形鏈中循環等待。

死鎖形成的條件以上四點缺一不可!接下來,我們嘗試依據這些條件寫一個死鎖的案例:

創建一張表 dead_lock_test,建表語句如下:

create table dead_lock_test
(
    id   int(11)     not null auto_increment,
    name varchar(20) not null,
    primary key (id)
) engine = InnoDB
  default charset = utf8;

Session1 我們輸入如下 sql:

-- T1 
begin;

select * from dead_lock_test where id = 1 for update;

-- T3
update dead_lock_test set id = id where id = 2;

Session2 我們輸入如下 sql:

-- T2
begin;

select * from dead_lock_test where id = 2 for update;

-- T4
update dead_lock_test set id = id where id = 1;

我們在 Session1 和 Session2 上按照 T1 ~ T4 的順序執行上面的 sql,Session2 客戶端最后顯示的報錯信息為:

mysql> update dead_lock_test set id = id where id = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

可以看到,InnoDB 行級鎖有可能導致死鎖現象的發生,不過 MyISAM 則不會出現死鎖,因為 MyISAM 只有表鎖,并不滿足死鎖出現的四個必要條件。

避免死鎖有以下幾種常見的策略:

  • 設置獲得鎖的超時時間
  • 避免長事務
  • 避免事務中的用戶交互
  • 降低隔離級別
  • ... ...

講完了什么是死鎖后,我們再來看一下什么是樂觀鎖,什么是悲觀鎖?

image

鎖按照使用的方式劃分即可分為樂觀鎖與悲觀鎖。

先講一下什么是悲觀鎖。悲觀鎖指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處于鎖定狀態。

悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)。

樂觀鎖則相比于悲觀鎖來說使用了更加寬松的機制,它往往不依賴于數據庫提供的鎖機制,而是使用程序人為地實現。我來舉個例子??:

我們有一個表:

create table test_optimistic_lock
(
    id      int(11) not null auto_increment,
    money   int(11)          default null,
    version int(11) not null default '0',
    primary key (id)
) engine = InnoDB
  default charset = utf8;

在這個表中有一個 version 字段,version 字段維護的是一個數據版本信息,我們每一次寫數據時,都會更新 version 字段;每一次讀數據時,都會查看當前的 version 是否已經更新。

test_optimistic_lock 表中現有數據如下:

mysql> select * from test_optimistic_lock;
+----+-------+---------+
| id | money | version |
+----+-------+---------+
|  1 |  1000 |       0 |
|  2 |  NULL |       0 |
|  3 |  NULL |       0 |
|  4 |  NULL |       0 |
+----+-------+---------+
4 rows in set (0.00 sec)

現在有一個進程 A 要對 id 為 2 的行的 money 做更新,將其值設置為 1000,他便會使用這樣的語句:

update test_optimistic_lock set money = 1000,version = 0 + 1 where version = 0 and id = 2;

可以看到,我們的策略是在每次對該表寫時,為了防止發生沖突,都會先去檢查 version 再做更新操作,如果更新成功的話,便讓 version + 1。

目前表中數據更新如下:

mysql> select * from test_optimistic_lock;
+----+-------+---------+
| id | money | version |
+----+-------+---------+
|  1 |  1000 |       0 |
|  2 |  1000 |       1 |
|  3 |  NULL |       0 |
|  4 |  NULL |       0 |
+----+-------+---------+
4 rows in set (0.00 sec)

如果進程 B 也要對 id 為 2 的行的 money 做更新,將其值設置為 2000,執行 sql 如下:

update test_optimistic_lock set money = 2000,version = 0 + 1 where version = 0 and id = 2;

很顯然,因為第二行的 version 值已經更新到了 1 ,這條語句是無法執行成功的。

我們使用了一種寬松的機制,改變了鎖的方式。悲觀鎖“悲觀地”認為數據訪問一定會被外界所修改,所以在 commit 之前就加好了鎖。這樣雖然安全得到了保障,但是也帶來了許多問題,譬如數據庫性能會造成很大的開銷(尤其在長事務),有可能出現死鎖等。而樂觀鎖則采用了一種“樂觀”的方式,它只有在數據 commit 時,才會進行排它性的檢查。不過樂觀鎖也不是沒有缺點,樂觀鎖適用于寫操作比較少的情況,即沖突很少發生的情況。如果經常發生沖突的話,使用樂觀鎖反而會影響性能,降低系統的吞吐量。

總結

本題又是一個涵蓋了巨多知識點的面試題,大家可以從我的解答中找到很多面試題的 answer,譬如:

  • MyISAM 與 InnoDB 的鎖機制有什么區別?
  • 什么是共享鎖?什么是排它鎖?
  • InnoDB 在什么時候使用行級鎖?什么時候使用表級鎖?
  • 什么是死鎖?死鎖的四個必要條件是什么?
  • 請寫出一個死鎖的案例?如何避免死鎖?
  • 什么是樂觀鎖,什么是悲觀鎖?
  • 等等...

如果你仔細閱讀了本文,相信你一定可以從文章中找到這些問題的所有答案~

4、MyISAM 與 InnoDB 存儲引擎有什么不同?如何選擇?


其實我們從數據庫篇開始,就一直在總結 MyISAM 與 InnoDB 這兩個存儲引擎的不同了,這里我來給大家總結一下:

image

除了以上的不同之處外,還有一點需要特殊說明一下。那就是 InnoDB 存儲引擎不會保存表的具體行數,而 MyISAM 則使用了一個變量來保存整張表有多少行。

所以,當我們執行:

select count(*) from T;

時,該語句沒有任何的 where 條件。InnoDB 執行的速度要比 MyISAM 慢很多,因為 InnoDB 需要走全表掃描來計算數據共有多少行~

那么對于 MyISAM 與 InnoDB 這兩種存儲引擎,我們該如何選擇呢?

MyISAM 適合的場景為:

  • 需要頻繁執行全表 count 語句
  • 對數據進行增刪改的頻率不高,查詢非常頻繁
  • 沒有事務

第一點我們已經解釋過了;第二點是因為 MyISAM 使用的是表級鎖,如果增刪改操作頻繁,那么就要頻繁地對整張表加鎖,這個性能開銷無疑是巨大的。而如果更新操作頻率不高,MyISAM 相比于 InnoDB 還有一個優勢,那就是 MyISAM 使用的索引是非聚簇索引,不用像 InnoDB 的普通索引查詢那樣需要進行會回表。第三點也無需解釋,MyISAM 不支持事務,如果你的業務需求需要事務,就應該使用 InnoDB 存儲引擎。

InnoDB 適合的場景為:

  • 數據增刪改查都比較頻繁
  • 可靠性要求高,需要支持事務

這兩點我就不再贅述了,相信大家都能理解:-)

總結

這是一道很好的前菜,面試官可以通過 MyISAM 與 InnoDB 的這些不同之處,向面試者繼續深入地提問二者索引的區別,鎖的區別等。

總結

又是不知不覺寫了快 7000 多字了......

本來想在這一篇文章中將 MySQL 事務相關的面試問題全部寫完的,不過這樣寫下去可能就要 2 萬字了......

總之,后續內容我會抓緊更新,感謝您的閱讀!

image

好啦,至此為止,這篇文章就到這里了~歡迎大家關注我的公眾號,在這里希望你可以收獲更多的知識,我們下一期再見!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容