前言
這是系列文章【 Java 面試八股文】數據庫篇的第二期。
【 Java 面試八股文】系列會陸續更新 Java 面試中的高頻問題,旨在從問題出發,理解 Java 基礎,數據結構與算法,數據庫,常用框架等。該系列前幾期文章可以通過點擊文末給出的鏈接進行查看~
按照慣例——首先要做幾點說明:
- 【 Java 面試八股文】中的面試題來源于社區論壇,書籍等資源;感謝使我讀到這些寶貴面經的作者們。
- 對于【 Java 面試八股文】中的每個問題,我都會盡可能地寫出我自己認為的“完美解答”。但是畢竟我的身份不是一個“真理持有者”,只是一個秉承著開源分享精神的 “knowledge transmitter” & 菜雞,所以,如果這些答案出現了錯誤,可以留言寫出你認為更好的解答,并指正我。非常感謝您的分享。
- 知識在于“融釋貫通”,而非“死記硬背”;現在市面上固然有很多類似于“Java 面試必考 300 題” 這類的文章,但是普遍上都是糟粕,僅講述其果,而不追其源;希望我的【 Java 面試八股文】可以讓你知其然,且知其所以然~
那么,廢話不多說,我們正式開始吧!
往期文章
數據庫篇(二)
1、如何定位并優化慢查詢 sql?
答
我們從定位到慢查詢 sql,再通過分析并進行優化的順序如下:
- 通過開啟慢日志定位到慢查詢的 sql
- 使用 explain 工具分析 sql
- 修改并優化 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_log
,slow_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 則在這張表上自動加上一個寫鎖。
共享鎖(讀鎖)與排它鎖(寫鎖)的兼容性如下表所示:
啥意思呢?在有多個用戶(線程)對 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)?
死鎖是指兩個或兩個以上的進程或線程在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。
如上圖中所示,線程 A 持有鎖 A,線程 B 持有鎖 B。兩個線程處于爭搶狀態,線程 A 等待線程 B 釋放鎖,線程 B 又等待線程 A 釋放它的資源,這樣相互等待就形成了死鎖。
形成死鎖必須滿足四個必要條件:
- 互斥條件
- 請求與保持條件
- 不剝奪條件
- 環路等待條件
我們接下來對這些名字逐一進行解釋。
互斥條件是指進程對所分配到的資源進行排它性使用。說白了就是在一段時間內,某個資源每次只能被一個進程所占用。
請求與保持條件是指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放,說人話就是“有了碗里的餅,還嚷嚷著要吃鍋里的湯”。
不剝奪條件是指進程已獲得的資源在未使用完之前,不能被剝奪,只能由自己釋放,說人話就是“自己拿到了就不給別人用,自己用完了才給別人”。
環路等待條件是指當發生死鎖時,必然存在著一個資源的請求環形鏈,若干進程在這個環形鏈中循環等待。
死鎖形成的條件以上四點缺一不可!接下來,我們嘗試依據這些條件寫一個死鎖的案例:
創建一張表 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 只有表鎖,并不滿足死鎖出現的四個必要條件。
避免死鎖有以下幾種常見的策略:
- 設置獲得鎖的超時時間
- 避免長事務
- 避免事務中的用戶交互
- 降低隔離級別
- ... ...
講完了什么是死鎖后,我們再來看一下什么是樂觀鎖,什么是悲觀鎖?
鎖按照使用的方式劃分即可分為樂觀鎖與悲觀鎖。
先講一下什么是悲觀鎖。悲觀鎖指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處于鎖定狀態。
悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)。
樂觀鎖則相比于悲觀鎖來說使用了更加寬松的機制,它往往不依賴于數據庫提供的鎖機制,而是使用程序人為地實現。我來舉個例子??:
我們有一個表:
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 這兩個存儲引擎的不同了,這里我來給大家總結一下:
除了以上的不同之處外,還有一點需要特殊說明一下。那就是 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 萬字了......
總之,后續內容我會抓緊更新,感謝您的閱讀!
好啦,至此為止,這篇文章就到這里了~歡迎大家關注我的公眾號,在這里希望你可以收獲更多的知識,我們下一期再見!