5.數(shù)據(jù)庫
要求: 能熟練使用、部署、調(diào)優(yōu)、問題排查、懂原理
1.關(guān)系型數(shù)據(jù)庫: MySQL/Oracle/PostSQL
1.InnoDB引擎,事務(wù)處理機制
由于mysql中有兩套日志機制,一套是存儲層的redo log,另一套是server層的binlog,每次更新數(shù)據(jù)都要對兩個日志進行更新。為了防止寫日志時只寫了其中一個而沒有寫另外一個,mysql使用了一個叫兩階段提交的方式保證事務(wù)的一致性
執(zhí)行流程:
- 首先執(zhí)行器會找引擎取ID=2這一行數(shù)據(jù)
- 拿到數(shù)據(jù)后會把數(shù)據(jù)進行+1操作,然后調(diào)用引擎接口把新數(shù)據(jù)寫入
- 引擎將數(shù)據(jù)更新到內(nèi)存中,并將操作記錄到redo log里,此時redo log處于prepare狀態(tài)。但它不會提交事務(wù),只是通知執(zhí)行器已經(jīng)完成任務(wù),可以隨時提交。
- 執(zhí)行器生成這個操作的binlog,并把binlog寫入磁盤
- 最后執(zhí)行器調(diào)用引擎的事務(wù)接口,把redo log改為提交狀態(tài),更新完成。
2.MySQL優(yōu)化
粗說:
加索引的時候盡量準確,避免造成不必要的鎖定影響其他查詢。
盡量減少給予范圍的數(shù)據(jù)檢索(間隙鎖),避免因為間隙鎖帶來的影響,鎖定了不該鎖定的記錄。
盡量控制事務(wù)的大小,減少鎖定的資源量和鎖定時間。
盡量使用較低級別的事務(wù)隔離,減少 MySQL 因為事務(wù)隔離帶來的成本。
細說:
- 頻繁變動的字段, 不適合建索引
- 離散度越高越好, 離散低不適合建索引
- like "asd%" 都不一定用索引,其他位置都不用索引查詢
- 比對規(guī)則從左往右, 離散高放前邊, 一個索引器反復(fù), 最左前綴原則
- 去掉冗余索引, 有聯(lián)合索引, 就不要單個的聯(lián)合索引的列
- 能用覆蓋索引(主鍵), 就千萬不要 select *, 速度至少快1倍
- 用or 索引會失效
3.MySQL鎖相關(guān)

鎖分類:
表級鎖:對整張表加鎖。開銷小,加鎖快;不會出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低。
行級鎖:對某行記錄加鎖。開銷大,加鎖慢;會出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度也最高。
頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間;會出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般。

表級鎖:
意向共享鎖(IS):事務(wù)打算給數(shù)據(jù)行加行共享鎖,事務(wù)在給一個數(shù)據(jù)行加共享鎖前,必須先取得該表的 IS 鎖。
意向排他鎖(IX):事務(wù)打算給數(shù)據(jù)行加行排他鎖,事務(wù)在給一個數(shù)據(jù)行加排他鎖前,必須先取得該表的 IX 鎖。
注:意向共享鎖和意向排他鎖是數(shù)據(jù)庫主動加的,不需要我們手動處理。對于 UPDATE、DELETE 和 INSERT 語句,InnoDB 會自動給數(shù)據(jù)集加排他鎖。
行級鎖:
共享鎖(S):當一個事務(wù)讀取一條記錄的時候,不會阻塞其他事務(wù)對同一記錄的讀請求,但會阻塞對其的寫請求。當讀鎖釋放后,才會執(zhí)行其他事務(wù)的寫操作。
例如:select … lock in share mode
排他鎖(X):當一個事務(wù)對一條記錄進行寫操作時,會阻塞其他事務(wù)對同一表的讀寫操作,當該鎖釋放后,才會執(zhí)行其他事務(wù)的讀寫操作。
共享鎖,排他鎖,意向共享鎖,意向排他鎖兼容圖例
如果一個事務(wù)請求的鎖模式與當前的鎖兼容, InnoDB 就將請求的鎖授予該事務(wù);反之, 如果兩者不兼容,該事務(wù)就要等待鎖釋放。

間隙鎖
前面談到行鎖是針對一條記錄進行加鎖。當對一個范圍內(nèi)的記錄加鎖的時候,我們稱之為間隙鎖。
當使用范圍條件索引數(shù)據(jù)時,InnoDB 會對符合條件的數(shù)據(jù)索引項加鎖。對于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做“間隙(GAP)”,InnoDB 也會對這個“間隙”加鎖,這就是間隙鎖。間隙鎖和行鎖合稱(Next-Key鎖)。
死鎖
兩個事務(wù)都需要獲得對方持有的排他鎖才能繼續(xù)完成任務(wù),這種互相等待對方釋放資源的情況就是死鎖。
避免死鎖:
- 第一步查詢,第二步更新.查詢時直接上排它鎖
- 同時需要查詢修改多個表,按同樣的順序進行加鎖
MySQL 鎖定情況的查詢
在實際開發(fā)中無法避免數(shù)據(jù)被鎖的問題,那么我們可以通過哪些手段來查詢鎖呢?
表級鎖可以通過兩個變量的查詢:
Table_locks_immediate,產(chǎn)生表級鎖的次數(shù)。
Table_locks_waited,數(shù)顯表級鎖而等待的次數(shù)。
行級鎖可以通過下面幾個變量查詢:
Innodb_row_lock_current_waits,當前正在等待鎖定的數(shù)量。
Innodb_row_lock_time(重要),從系統(tǒng)啟動到現(xiàn)在鎖定總時長。
Innodb_row_lock_time_avg(重要),每次等待所花平均時間。
Innodb_row_lock_time_max,從系統(tǒng)啟動到現(xiàn)在等待最長的一次花費時間。
Innodb_row_lock_waits(重要),從系統(tǒng)啟動到現(xiàn)在總共等待的次數(shù)。
特別注意,行鎖使用不當會造成排它鎖:
原文鏈接:https://blog.csdn.net/claram/java/article/details/54023216
1、只根據(jù)主鍵進行查詢,并且查詢到數(shù)據(jù),主鍵字段產(chǎn)生行鎖。
begin;
select * from goods where id = 1 for update;
commit;
2、只根據(jù)主鍵進行查詢,沒有查詢到數(shù)據(jù),不產(chǎn)生鎖。
begin;
select * from goods where id = 1 for update;
commit;
3、根據(jù)主鍵、非主鍵含索引(name)進行查詢,并且查詢到數(shù)據(jù),主鍵字段產(chǎn)生行鎖,name字段產(chǎn)生行鎖。
begin;
select * from goods where id = 1 and name='prod11' for update;
commit;
4、根據(jù)主鍵、非主鍵含索引(name)進行查詢,沒有查詢到數(shù)據(jù),不產(chǎn)生鎖。
begin;
select * from goods where id = 1 and name='prod12' for update;
commit;
5、根據(jù)主鍵、非主鍵不含索引(name)進行查詢,并且查詢到數(shù)據(jù),如果其他線程按主鍵字段進行再次查詢,則主鍵字段產(chǎn)生行鎖,如果其他線程按非主鍵不含索引字段進行查詢,則非主鍵不含索引字段產(chǎn)生表鎖,如果其他線程按非主鍵含索引字段進行查詢,則非主鍵含索引字段產(chǎn)生行鎖,如果索引值是枚舉類型,mysql也會進行表鎖,這段話有點拗口,大家仔細理解一下。
begin;
select * from goods where id = 1 and name='prod11' for update;
commit;
6、根據(jù)主鍵、非主鍵不含索引(name)進行查詢,沒有查詢到數(shù)據(jù),不產(chǎn)生鎖。
begin;
select * from goods where id = 1 and name='prod12' for update;
commit;
7、根據(jù)非主鍵含索引(name)進行查詢,并且查詢到數(shù)據(jù),name字段產(chǎn)生行鎖。
begin;
select * from goods where name='prod11' for update;
commit;
8、根據(jù)非主鍵含索引(name)進行查詢,沒有查詢到數(shù)據(jù),不產(chǎn)生鎖。
begin;
select * from goods where name='prod11' for update;
commit;
9、根據(jù)非主鍵不含索引(name)進行查詢,并且查詢到數(shù)據(jù),name字段產(chǎn)生表鎖。
begin;
select * from goods where name='prod11' for update;
commit;
10、根據(jù)非主鍵不含索引(name)進行查詢,沒有查詢到數(shù)據(jù),name字段產(chǎn)生表鎖。
begin;
select * from goods where name='prod11' for update;
commit;
11、只根據(jù)主鍵進行查詢,查詢條件為不等于,并且查詢到數(shù)據(jù),主鍵字段產(chǎn)生表鎖。
begin;
select * from goods where id <> 1 for update;
commit;
12、只根據(jù)主鍵進行查詢,查詢條件為不等于,沒有查詢到數(shù)據(jù),主鍵字段產(chǎn)生表鎖。
begin;
select * from goods where id <> 1 for update;
commit;
13、只根據(jù)主鍵進行查詢,查詢條件為 like,并且查詢到數(shù)據(jù),主鍵字段產(chǎn)生表鎖。
begin;
select * from goods where id like '1' for update;
commit;
14、只根據(jù)主鍵進行查詢,查詢條件為 like,沒有查詢到數(shù)據(jù),主鍵字段產(chǎn)生表鎖。
begin;
select * from goods where id like '1' for update;
commit;
樂觀鎖、悲觀鎖
悲觀鎖方案:每次獲取商品時,對該商品加排他鎖。也就是在用戶A獲取獲取 id=1 的商品信息時對該行記錄加鎖,期間其他用戶阻塞等待訪問該記錄。悲觀鎖適合寫入頻繁的場景。
樂觀鎖方案:每次獲取商品時,不對該商品加鎖。在更新數(shù)據(jù)的時候需要比較程序中的庫存量與數(shù)據(jù)庫中的庫存量是否相等,如果相等則進行更新,反之程序重新獲取庫存量,再次進行比較,直到兩個庫存量的數(shù)值相等才進行數(shù)據(jù)更新。樂觀鎖適合讀取頻繁的場景
商城瀏覽商品下單這種高并發(fā)場景下,一般采用讀寫分離、異構(gòu)數(shù)據(jù)庫結(jié)構(gòu),讀從Redis等緩存庫里讀,并發(fā)下單操作,鎖機制可以用lua腳本、Redisson、zookpeer等方式做分布式鎖、或者MySQL的排它鎖,同時要防止死鎖
事務(wù)隔離級別
讀未提交(read uncommitted):它是最低的事務(wù)隔離級別,一個事務(wù)還沒提交時,它做的變更就能被別的事務(wù)看到。有臟讀的可能性。
讀提交(read committed):保證一個事物提交后才能被另外一個事務(wù)讀取。另外一個事務(wù)不能讀取該事物未提交的數(shù)據(jù)。可避免臟讀的發(fā)生,但是可能會造成不可重復(fù)讀。
可重復(fù)讀(repeatable read MySQL 默認方式):多次讀取同一范圍的數(shù)據(jù)會返回第一次查詢的快照,即使其他事務(wù)對該數(shù)據(jù)做了更新修改。事務(wù)在執(zhí)行期間看到的數(shù)據(jù)前后必須是一致的。即:事務(wù)A在讀到一條數(shù)據(jù)之后,此時事務(wù)B對該數(shù)據(jù)進行了修改并提交,那么事務(wù)A再讀該數(shù)據(jù),讀到的還是原來的內(nèi)容.
串行化(serializable):是最可靠的事務(wù)隔離級別。“寫”會加“排他鎖”,“讀”會加“共享鎖”。