1. MySQL的索引
1.1 索引的數(shù)據(jù)結(jié)構(gòu)
B+樹
多路平衡查找樹询筏,路數(shù)(degree) = 數(shù)據(jù)頁一頁大小 / 索引數(shù)據(jù)大小
MySql 默認一頁大小是16k = 4 * 操作系統(tǒng)頁(4KB)
1.2 MySQL常見的存儲引擎
MyISAM
tableName.frm(建表文件)
tableName.myd(my-data 存放數(shù)據(jù))
tableName.myi (my-index 存放索引)
InnoDB
tableName.frm
tableName.idb
可以看出 InnoDB數(shù)據(jù)和索引是放在同一個文件里渠鸽,按照主鍵來組織數(shù)據(jù)的存儲涝涤。所以主鍵索引才又叫聚集索引训柴。
如果創(chuàng)建表沒有指定哪一列做索引龙助,InnoDB將自動生成一個6byte int型的隱藏主鍵捉邢。
主鍵索引的葉子節(jié)點數(shù)據(jù)區(qū)保存數(shù)據(jù),其他索引的葉子節(jié)點數(shù)據(jù)區(qū)保存主鍵
CSV
tableName.frm
tableName.CSV (保存元數(shù)據(jù))
tableName.CSM (數(shù)據(jù)行吮螺,可以用vi直接編輯)
1.3 索引選擇原則
經(jīng)常用的列優(yōu)先(最左匹配原則)
離散度高的列優(yōu)先
寬度小的列優(yōu)先(一個數(shù)據(jù)頁的degree越多饶囚,提高樹的查找效率)
覆蓋索引:要查詢的值正好包含在索引中,從而不需要找到葉子節(jié)點鸠补。所以如果查詢能通過命中聯(lián)合索引直接返回的萝风,一定不要用select *;
2. MySQL查詢執(zhí)行路徑
客戶端發(fā)送查詢請求
查詢緩存(可配可無)
查詢優(yōu)化處理
查詢執(zhí)行引擎
返回客戶端
2.1 客戶端與服務(wù)端的通信
半雙工: 在任意一個時刻,要么是客戶端接收服務(wù)端返回的數(shù)據(jù)紫岩,要么是客戶端向服務(wù)端發(fā)送數(shù)據(jù)规惰,這兩個動作不能同時發(fā)生。也無法將一個消息切成小塊進行傳輸被因。
客戶端一旦開始接收數(shù)據(jù)就沒有辦法停下來發(fā)送指令卿拴。
對于一個MySQL連接衫仑,時刻都有一個狀態(tài)來標識這個連接正在做什么
show full processlist / show processlist
常見狀態(tài)有:
- Sleep : 線程正在等待客戶端發(fā)送數(shù)據(jù)
- Query:連接線程正在執(zhí)行查詢
- Locked: 線程正在等待表鎖的釋放
- Sorting result: 線程正在對結(jié)果進行排序
- Sending data : 向請求端返回數(shù)據(jù)
2.2 緩存
MySQL能緩存select操作的結(jié)果集和SQL語句梨与。對于新的SELECT語句,先去查緩存文狱,判斷是否有可用的記錄集粥鞋。
判斷標準為:與緩存的SQL語句完全一樣,區(qū)分大小寫瞄崇。即必須一毛一樣呻粹,可以簡單認為存儲了一個key-value的結(jié)構(gòu),key為SQL語句苏研,value為SQL執(zhí)行結(jié)果集等浊。
可以通過如下命令查看緩存情況
show status like 'Qcache%'
緩存配置項:
-
query_cache_type
0 - 不啟用 (默認值)
1- 啟用查詢緩存。但可以通過加上SQL_NO_CACHE不緩存
2- 啟用摹蘑。但需要在SQL上加SQL_CACHE才緩存
-
query_cache_size
緩存區(qū)的大小筹燕,默認1M。推薦64/128M
-
query_cache_limit
單個結(jié)果集可使用的緩存大小衅鹿,結(jié)果大于該值則不緩存撒踪。默認1M
為什么mysql默認關(guān)閉了緩存呢?
- 在查詢之前要先檢查是否命中緩存大渤,浪費計算資源
- 如果這個查詢可以被緩存制妄,那么執(zhí)行完成后,MySQL發(fā)現(xiàn)緩存中沒有這個查詢泵三,將會把結(jié)果集放入緩存中耕捞,帶來額外的系統(tǒng)消耗
- 最重要的一點衔掸,當對表進行寫入和更新操作時,該表對應(yīng)的所有緩存都將失效
所以俺抽,MySQL的緩存只適合以讀為主的業(yè)務(wù)場景具篇,數(shù)據(jù)生成之后就不常變的業(yè)務(wù)。比如 新聞凌埂、報表類驱显、門戶類等
2.3 查詢優(yōu)化處理
2.3.1 查詢優(yōu)化處理的三個階段
-
解析SQL
將sql語句解析成解析樹
-
預(yù)處理階段
根據(jù)MySQL的語法規(guī)則進一步檢查解析樹的合法性。比如檢查表和數(shù)據(jù)列是否存在瞳抓,解析列名和別名等
-
查詢優(yōu)化器
找到最優(yōu)的執(zhí)行計劃
那么埃疫,查詢優(yōu)化器是怎么找到最優(yōu)的執(zhí)行計劃呢?
MySQL的查詢優(yōu)化器是基于成本計算的原則孩哑。它會嘗試各種執(zhí)行計劃數(shù)據(jù)抽樣的方式進行試驗栓霜,隨機讀取一個4K的數(shù)據(jù)塊進行分析。
一些優(yōu)化手段
- 等價變化規(guī)則: 基于聯(lián)合索引横蜒,調(diào)整位置等
- 優(yōu)化count胳蛮、min、max等函數(shù):
- 覆蓋索引掃描
- 子查詢優(yōu)化
- 提前終止查詢 : limit
- IN的優(yōu)化 : 先進行排序丛晌,再采用二分查找
2.3.2 怎么看執(zhí)行計劃
可以通過explain查看執(zhí)行計劃仅炊,執(zhí)行計劃的幾個關(guān)鍵參數(shù):
-
id
id代表select查詢的序號,標識執(zhí)行的順序
- id相同澎蛛,執(zhí)行順序由上往下抚垄。id相同的可以認為是一組
- id不同,如果是子查詢谋逻,id的序號會遞增呆馁,id值越大優(yōu)先級越高,越先執(zhí)行
-
select_type
select_type標識查詢的類型毁兆,可以區(qū)分普通查詢浙滤、聯(lián)合查詢、子查詢等
- SIMPLE :簡單的select查詢气堕,查詢中不包含子查詢或者union查詢
- PRIMARY: 對于包含子查詢的sql纺腊,外層查詢將被標記為PRIMARY
- SUBQUERY:表示在select或where列表中包含子查詢
- MATERIALIZED: 表示在where后面 in條件的子查詢
- UNION:若第二個select出現(xiàn)在union之后,則被標記為union
- UNION RESULT: 從union表獲取結(jié)果的select
-
table
查詢設(shè)計到的表送巡,一般直接顯示表名或別名
- <unionM,N>由id為M,N查詢聯(lián)合產(chǎn)生的結(jié)果
- <subqueryN> 由id為N的查詢產(chǎn)生的結(jié)果
-
type
訪問類型摹菠,它在查詢優(yōu)化中是一個很重要的指標,結(jié)果從好到壞依次是:
- system:表只有一行記錄骗爆,或系統(tǒng)表次氨,
- const: 表示通過一次索引就找到了,用于比較primary key或者unique key
- eq_ref:唯一索引掃描摘投,對于每個索引健煮寡,表中只有一條記錄與之匹配
- ref:非唯一索引掃描虹蓄,返回匹配某個單獨值的所有行
- range : 只檢索給定范圍的行
- index:full index scan。索引全表掃描
- ALL :full table scan幸撕。
-
possible_keys, key, rows, filtered
- possible_keys :查詢過程中有可能用到的索引
- key: 實際使用的索引
- rows:大致估算找到所需記錄要讀取的行數(shù)
- filtered:結(jié)果行數(shù)占所需要讀取rows行數(shù)的百分比薇组。filtered越大越好
2.3.3 Extra
執(zhí)行計劃中的額外信息
-
using filesort
MySQL對數(shù)據(jù)使用一個外部的文件內(nèi)容進行排序,而不是按照表內(nèi)的索引進行排序讀取
-
Using temporary
使用臨時表保存中間結(jié)果坐儿,常見于order by 或 group by
-
Using index:
表示select操作中使用了覆蓋索引律胀,避免了訪問表的數(shù)據(jù)行
-
Using where
使用了where過濾條件
-
select tables optimized away
基于索引優(yōu)化MIN/MAX操作或者MyISAM存儲引擎優(yōu)化COUNT(*)操作, 不必等到執(zhí)行階段在進行計算貌矿, 查詢執(zhí)行計劃生成的階段即可完成優(yōu)化
2.4 查詢執(zhí)行引擎
調(diào)用插件式的存儲引擎API執(zhí)行 執(zhí)行計劃
2.5 返回客戶端
需要做緩存的炭菌,執(zhí)行緩存操作
-
增量的返回結(jié)果給客戶端:
開始生成第一條結(jié)果時,MySQL就開始往請求方逐步返回數(shù)據(jù)逛漫。
這么做的好處是:MySQL服務(wù)器無需保存過多的數(shù)據(jù)黑低,從而浪費內(nèi)存;用戶體驗好酌毡,拿到數(shù)據(jù)快
3. 如何定位慢SQL
3.1.1 慢查詢?nèi)罩九渲?/h5>
#查看是否開啟慢查詢?nèi)罩?show variables like 'slow_query_log'
#開啟慢查詢?nèi)罩?set global slow_query_log=on
#設(shè)置慢查詢?nèi)罩疚募窂?set global slow_query_log_file='/var/lib/mysql/slow.log'
#將沒有使用索引的查詢記錄到日志
set global log_queries_not_using_indexes=on
#設(shè)置慢查詢閾值克握,單位:秒
set global long_query_time=10
3.2.2 慢查詢?nèi)罩痉治龉ぞ?/h5>
mysqldumpslow --help
4. 事務(wù)
事務(wù)是數(shù)據(jù)庫操作的最小工作單元,是一組不可再分的邏輯工作單元枷踏。
#開啟事務(wù)
begin/start transaction --手工開啟一個事務(wù)
#事務(wù)提交或回滾
commit/rollback
#設(shè)置事務(wù)自動開啟
set session autocommit = on/off;
4.1 事務(wù)的ACID特性
-
Automicity 原子性
最小的工作單元菩暗,整個工作單元要么一起提交成功,要么全部失敗回滾
-
Consistency 一致性
事務(wù)中操作的數(shù)據(jù)及狀態(tài)改變是一致的呕寝,即寫入資料的結(jié)果必須完全符合預(yù)設(shè)的規(guī)則婴梧。不會因為出現(xiàn)系統(tǒng)意外等原因?qū)е聽顟B(tài)的不一致
-
Isolation 隔離性
一個事務(wù)所操作的數(shù)據(jù)在提交之前孽江,對其他事務(wù)的可見性設(shè)定(一般設(shè)定為不可見)
-
Durability 持久性
事務(wù)所作的修改就會永久保存,不會因為系統(tǒng)意外導(dǎo)致數(shù)據(jù)的丟失
4.2 事務(wù)并發(fā)帶來的問題
-
臟讀 :事務(wù)A讀到事務(wù)B 本該rollback的 內(nèi)容
![臟讀]
臟讀.jpg
-
不可重復(fù)讀:
事務(wù)A先讀取某一條數(shù)據(jù),在執(zhí)行自己邏輯的過程中暇屋,事務(wù)B修改了這條數(shù)據(jù)并提交事務(wù)昙衅。然后事務(wù)A再次讀取該記錄,發(fā)現(xiàn)兩次數(shù)據(jù)不一致了啼县。
不可重復(fù)讀.jpg
- 幻讀
幻讀.jpg
4.3 事務(wù)的隔離級別
#查看是否開啟慢查詢?nèi)罩?show variables like 'slow_query_log'
#開啟慢查詢?nèi)罩?set global slow_query_log=on
#設(shè)置慢查詢?nèi)罩疚募窂?set global slow_query_log_file='/var/lib/mysql/slow.log'
#將沒有使用索引的查詢記錄到日志
set global log_queries_not_using_indexes=on
#設(shè)置慢查詢閾值克握,單位:秒
set global long_query_time=10
mysqldumpslow --help
4. 事務(wù)
事務(wù)是數(shù)據(jù)庫操作的最小工作單元,是一組不可再分的邏輯工作單元枷踏。
#開啟事務(wù)
begin/start transaction --手工開啟一個事務(wù)
#事務(wù)提交或回滾
commit/rollback
#設(shè)置事務(wù)自動開啟
set session autocommit = on/off;
4.1 事務(wù)的ACID特性
-
Automicity 原子性
最小的工作單元菩暗,整個工作單元要么一起提交成功,要么全部失敗回滾
-
Consistency 一致性
事務(wù)中操作的數(shù)據(jù)及狀態(tài)改變是一致的呕寝,即寫入資料的結(jié)果必須完全符合預(yù)設(shè)的規(guī)則婴梧。不會因為出現(xiàn)系統(tǒng)意外等原因?qū)е聽顟B(tài)的不一致
-
Isolation 隔離性
一個事務(wù)所操作的數(shù)據(jù)在提交之前孽江,對其他事務(wù)的可見性設(shè)定(一般設(shè)定為不可見)
-
Durability 持久性
事務(wù)所作的修改就會永久保存,不會因為系統(tǒng)意外導(dǎo)致數(shù)據(jù)的丟失
4.2 事務(wù)并發(fā)帶來的問題
-
臟讀 :事務(wù)A讀到事務(wù)B 本該rollback的 內(nèi)容
![臟讀]臟讀.jpg
-
不可重復(fù)讀:
事務(wù)A先讀取某一條數(shù)據(jù),在執(zhí)行自己邏輯的過程中暇屋,事務(wù)B修改了這條數(shù)據(jù)并提交事務(wù)昙衅。然后事務(wù)A再次讀取該記錄,發(fā)現(xiàn)兩次數(shù)據(jù)不一致了啼县。
- 幻讀
4.3 事務(wù)的隔離級別
根據(jù)SQL92 ANSI/ISO標準:
-
Read Uncommitted (未提交讀谭羔、讀未提交)
事務(wù)未提交對其他事務(wù)也是可見的瘟裸,未解決任何并發(fā)問題
-
Read Committed 提交讀
一個事務(wù)開始之后话告,只能看到已提交的事務(wù)所做的修改。解決了臟讀問題病线,存在不可重復(fù)讀
-
Repeatable Read 可重復(fù)度
在同一個事務(wù)中暖眼,多次讀取同樣的數(shù)據(jù)結(jié)果是一樣的司澎,解決不可重復(fù)度問題挤安。但這種隔離級別未定義解決幻讀的問題
-
Serializable 串行化
最高的隔離級別蛤铜,每個事務(wù)串行執(zhí)行 ,解決所有問題
總結(jié):
隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|
未提交讀(Read Uncommitted) | 可能 | 可能 | 可能 |
已提交讀(Read Committed) | 不可能 | 可能 | 可能 |
可重復(fù)讀(Repeatable Read) | 不可能 | 不可能 | 對InnoDB不可能 |
串行化(Serializable) | 不可能 | 不可能 | 不可能 |
5. 鎖
鎖是用來管理不同事務(wù)對共享資源的并發(fā)訪問
5.1 表鎖昂羡、行鎖
表鎖與行鎖的區(qū)別:
鎖定粒度:表鎖 > 行鎖
加鎖效率:表鎖 > 行鎖
沖突概率:表鎖 > 行鎖
并發(fā)性能:表鎖 < 行鎖
InnoDB存儲引擎支持行鎖和表鎖(另類的行鎖)
5.2 InnoDB的鎖
- 共享鎖(行鎖): Shared Locks
- 排他鎖(行鎖):Exclusive Locks
- 意向共享鎖(表鎖):Intention Shared Locks
- 意向排他鎖(表鎖):Intention Exclusive Locks
- 自增鎖: AUTO-INC Locks
其中,行鎖的算法
- 記錄鎖 Record Locks
- 間隙鎖 Gap Locks
- 臨鍵鎖 Next-key Locks
5.2.1 共享鎖 VS 排他鎖
- 共享鎖
共享鎖又稱讀鎖虐先,簡稱S(shared)鎖。顧名思義蛹批,共享鎖是多個事務(wù)對于同一數(shù)據(jù)可以共享一把鎖撰洗,都能訪問到數(shù)據(jù)差导,但是只能讀不能修改。
加鎖方式:
select * from users where id = 1 Lock IN SHARE MODE;
- 排他鎖
排他鎖又稱寫鎖,簡稱X鎖椅您,排他鎖不能與其他鎖并存雪隧,如一個事務(wù)獲取了一個數(shù)據(jù)行的排他鎖,其他事務(wù)就不能再獲取該行的鎖(不管是共享鎖還是排他鎖)员舵。只有該獲取了排他鎖的事務(wù)可以對這行數(shù)據(jù)進行讀取和修改脑沿。其他事務(wù)要讀取數(shù)據(jù)可來自于快照。
加鎖方式:
# for update 加X鎖固灵。 delete/update/insert默認加X鎖
select * from users where id = 1 for update;
InnoDB的行鎖到底鎖了什么捅伤?
InnoDB的行鎖是通過給索引上的索引項加鎖來實現(xiàn)的。只有通過索引條件進行數(shù)據(jù)檢索巫玻,InnoDB才使用行級鎖,否則祠汇,InnoDB將使用表鎖(鎖住索引的所有記錄)
5.2.2 意向共享鎖 和意向排他鎖
-
意向共享鎖 IS:
表示事務(wù)準備給數(shù)據(jù)行加入共享鎖仍秤。即,一個數(shù)據(jù)行要想加共享鎖之前可很,必須先取得該表的意向共享鎖诗力,意向共享鎖之間是可以互相兼容的。
-
意向排他鎖 IX:
表示事務(wù)準備給數(shù)據(jù)行加入排他鎖。即苇本,一個數(shù)據(jù)行加排他鎖之前必須先取得該表的IX鎖袜茧,意向排他鎖之間是可以相互兼容的。
意向鎖是InnoDB操作數(shù)據(jù)之前自動加的瓣窄,不需要用戶干預(yù)笛厦。當一個事務(wù)想去進行表鎖時,可以先判斷意向鎖是否存在俺夕,如果存在則可以快速返回該表不能啟用表鎖裳凸。
5.2.3 自增鎖 AUTO-INC Locks
自增鎖是針對自增列自增長的一個特殊的表級別鎖
show variables like 'innodb_autoinc_lock_mode'
默認取值為1,代表連續(xù)劝贸,若事務(wù)未提交則ID永久丟失
5.3 鎖的算法
前面我們說InnoDB的行鎖是采用記錄鎖姨谷、間隙鎖、臨鍵鎖來實現(xiàn)的映九,那么這三者又是什么呢梦湘?
5.3.1 記錄鎖 Record Locks
記錄鎖的功能是鎖住具體的索引項。當sql執(zhí)行按照唯一性索引(Primary Key 件甥、Unique Key)進行數(shù)據(jù)的檢索時践叠,查詢條件等值匹配且要查詢的數(shù)據(jù)存在,這時SQL語句加上的鎖即為記錄鎖Record Locks嚼蚀,鎖住這行索引項禁灼。
例如:數(shù)據(jù)庫中存在2條記錄
id | name | age |
---|---|---|
1 | Alice | 20 |
2 | Bob | 22 |
事務(wù)A鎖住id為1的記錄即給主鍵索引為1的記錄加了一個記錄鎖。
5.3.2 間隙鎖 Gap Locks
當sql執(zhí)行按照索引進行數(shù)據(jù)的檢索時轿曙,若查詢條件的數(shù)據(jù)不存在弄捕,這時SQL語句加上的鎖即為Gap Locks , 鎖住索引不存在的這段區(qū)間,且左開右開导帝。
如:數(shù)據(jù)庫user表存在3條記錄:
id | name | age |
---|---|---|
1 | Alice | 20 |
2 | Bob | 22 |
5 | Charlie | 22 |
事務(wù)A嘗試獲取id=4 的鎖守谓,sql語句為:
# 事務(wù)A
begin;
select * from users where id = 4 for update;
#先獲取鎖 rollback先不執(zhí)行
rollback;
然后事務(wù)B嘗試插入 id = 3的記錄:
# 事務(wù)B
set session autocommit = off; --設(shè)置會話不自動提交
insert into users values(3,"Eve",20);
rollback;
結(jié)果發(fā)現(xiàn)插表不成功,輸出為:
1205 - Lock wait timeout exceeded; try restarting transaction
時間: 51.035s
再嘗試把id=3 改為 id=4 同樣插不進表您单。更新id=2 或 id=5 均可成功斋荞。由此可以驗證結(jié)論為:間隙是指不存在的那段區(qū)間。鎖住的范圍是左開右開虐秦。 無窮大同理平酿。
5.3.3 臨鍵鎖 Next-Key Locks
臨鍵鎖鎖住的是 記錄+區(qū)間(左開右閉)。當sql執(zhí)行按照索引進行數(shù)據(jù)的檢索時悦陋,若查詢的條件為范圍查找(between a and b蜈彼、< 、>)并有數(shù)據(jù)命中俺驶,則此時的SQL語句加上鎖為臨鍵鎖幸逆。鎖住索引存在的那條記錄 + 不存在的區(qū)間。左開右閉。
所以还绘,InnoDB可以解決幻讀楚昭,當根據(jù)條件范圍檢索時,檢索范圍內(nèi)不存在的數(shù)據(jù)將被鎖住拍顷,所以不可能插入新記錄進去抚太。
InnoDB采用臨鍵鎖作為行鎖的默認算法。
當記錄不存在菇怀,臨鍵鎖可退化成Gap鎖凭舶;當條件精準匹配到記錄,則退化成Record鎖爱沟。
5.4 數(shù)據(jù)庫的死鎖
死鎖發(fā)生的條件:
- 多個并發(fā)事務(wù)(2個及以上)
- 每個事務(wù)都持有鎖或在等待鎖
- 每個事務(wù)都需要繼續(xù)再持有鎖
- 事務(wù)之間產(chǎn)生加鎖的循環(huán)等待帅霜,形成死鎖
避免死鎖:
類似的業(yè)務(wù)邏輯以固定的順序訪問表和數(shù)據(jù)行
大事務(wù)拆成小事務(wù)。一般大事務(wù)更容易產(chǎn)生死鎖呼伸,業(yè)務(wù)允許的話把大事務(wù)拆成小事務(wù)
在同一個事務(wù)中身冀,盡可能做到一次性鎖定所有需要的資源,減少死鎖概率
如果業(yè)務(wù)允許可以降低隔離級別
為表添加合理的索引括享÷Ц可以看到如果不走索引將會為表添加表鎖。
了解完鎖的特性后铃辖,再看下面這個例子:
begin;--事務(wù)A
update users set age = 0 where id = 1; -- 上X鎖
select * from users where id =1; -- 結(jié)果: 1剩愧,Alice,0
-- age = 0的數(shù)據(jù)存到哪兒去了?
rollback;
事務(wù)B在事務(wù)A 提交或rollBack之前娇斩,查到的永遠是正確的1仁卷,Alice,20犬第。
是不是有這樣的疑問:為什么給id=1 的記錄加上X鎖后锦积,別的事務(wù)還是能對它進行查詢呢?快照是什么歉嗓?它怎么實現(xiàn)的丰介?
6. MVCC
MVCC ,Multi version concurrency control 鉴分,多版本并發(fā)控制哮幢。
并發(fā)訪問數(shù)據(jù)庫時,對正在事務(wù)內(nèi)處理的數(shù)據(jù)做多版本的管理冠场,以避免寫操作的堵塞造成無法讀的并發(fā)問題家浇。
6.1 增刪改查對應(yīng)的MVCC
MVCC: 一張表設(shè)計完以后,MySQL默認給每一行自動生成DB_TRX_ID(數(shù)據(jù)入庫時事務(wù)版本號)碴裙,DB_ROLL_PT(數(shù)據(jù)刪除時的版本號)。
6.1.1 插入
假設(shè)當前的全局事務(wù)ID為1,
begin; -- 拿到當前系統(tǒng)的事務(wù)ID
insert users(id,name,age) values(1,"Alice",20);
insert users(id,name,age) values(2,"Bob",21);
commit;
此時舔株,表中記錄的包括版本相關(guān)的信息為:
id | name | age | DB_TRX_ID | DB_ROLL_PT |
---|---|---|---|---|
1 | Alice | 20 | 1 | NULL |
2 | Bob | 21 | 1 | NULL |
6.1.2 刪除
假設(shè)當前的系統(tǒng)全局事務(wù)ID已經(jīng)到了10. 再此之前沒有其他事務(wù)操作過id=1 的記錄
begin; -- 拿到事務(wù)ID=10
delete users where id = 1;
commit;
事務(wù)提交后莺琳,id = 1的記錄 DB_ROLL_PT = 10. MVCC相關(guān)信息變成:
id | name | age | DB_TRX_ID | DB_ROLL_PT |
---|---|---|---|---|
1 | Alice | 20 | 1 | 10 |
2 | Bob | 21 | 1 | NULL |
6.1.3 修改
修改操作是先做命中數(shù)據(jù)行的復(fù)制,然后將原來那行數(shù)據(jù)的刪除版本號設(shè)置為當前的事務(wù)ID载慈。假設(shè)當前全局ID到了20惭等,事務(wù)執(zhí)行:
begin; -- 拿到事務(wù)ID=20
update users set age = 20 where id = 2;
commit;
事務(wù)提交后,
id | name | age | DB_TRX_ID | DB_ROLL_PT |
---|---|---|---|---|
1 | Alice | 20 | 1 | 10 |
2 | Bob | 21 | 1 | 20 |
2 | Bob | 20 | 20 | NULL |
6.1.4 查詢
查詢數(shù)據(jù)行版本小于當前查詢的事務(wù)版本办铡,這樣就確保了只能讀取到在本次事務(wù)開始之前就已經(jīng)存在的數(shù)據(jù)辞做;另外,數(shù)據(jù)行的刪除版本要么為NULL寡具,要么大于本次查詢版本號秤茅,確保取出的記錄在本次查詢開始之前沒有被刪除。
但是如果事務(wù)A(tx_id=2)查詢id=1的數(shù)據(jù)童叠,然后處理自己邏輯的同時框喳,事務(wù)B(tx_id = 1)對id=1的數(shù)據(jù)修改并提交了,接著事務(wù)A再次查詢了id=1的數(shù)據(jù)厦坛,MVCC是怎么解決不可重復(fù)讀的五垮?
6.2 Redu / Undo 日志
6.2.1 Undo Log
Undo意思是撤銷,以撤銷操作為目的杜秸,返回某個指定狀態(tài)的操作放仗。
Undo Log是指在事務(wù)開始以前,在任何操作開始之前撬碟,首先將需要操作的數(shù)據(jù)備份到一個地方(Undo Log)诞挨。
Undo Log是為了實現(xiàn)事務(wù)的原子性而出現(xiàn)的產(chǎn)物。事務(wù)處理過程中如果出現(xiàn)了錯誤或用戶執(zhí)行ROLLBACK語句小作,MySQL可以利用Undo Log中的備份將數(shù)據(jù)恢復(fù)到事務(wù)開始之前的狀態(tài)亭姥。即,Undo Log中的數(shù)據(jù)可以作為數(shù)據(jù)舊版本的快照 供其他并發(fā)事務(wù)進行快照讀顾稀。
6.2.2 Redo Log
Redo 的意思是重做达罗。Redo Log 是事務(wù)操作的任何數(shù)據(jù),將最新的數(shù)據(jù)備份到一個地方(Redo Log)静秆。
Redo Log不是隨著事務(wù)的提交才寫的粮揉,而是在事務(wù)的執(zhí)行過程中就開始寫入Redo Buffer中,具體什么時候?qū)懙酱疟P的Redo Log可以通過配置來配抚笔。Innodb_flush_log_at_trx_commit:
- 0:每秒提交redo buffer->redo log -> flush cache to disk
- 1:每次事務(wù)提交執(zhí)行全流程redo buffer->redo log -> flush cache to disk
- 2:每次事務(wù)提交執(zhí)行redo buffer->redo log扶认。 然后每隔一秒執(zhí)行 flush cache to disk
Redo Log是為了確保事務(wù)的持久性而設(shè)計的。為了防止在故障發(fā)生的時候殊橙,尚有臟頁未寫入磁盤辐宾,在重啟MySQL服務(wù)時狱从,根據(jù)Redo Log進行重做,從而使未入磁盤的數(shù)據(jù)再次進行持久化叠纹。
7. 數(shù)據(jù)庫表設(shè)計 三范式
第一范式(1NF):字段具有原子性季研,不可再分。
第二范式(2NF):屬性完全依賴主鍵誉察,不能出現(xiàn)僅依賴主鍵一部分的屬性与涡,如果出現(xiàn)了,那么這個屬性和主關(guān)鍵字的這部分關(guān)系應(yīng)該分離出來形成一個新的實體持偏。
-
第三范式(3NF):一個數(shù)據(jù)庫表中不包含已在其他表中已包含的非主鍵信息驼卖。
簡單點說:
1NF:每一列只有一個單一的值,不能再拆分
2NF:每一行都有主鍵進行區(qū)分
3NF:每一個表都不包含其他表已經(jīng)存在的非主鍵信息
三范式的要求僅為庫表設(shè)計提供一種理論依據(jù)鸿秆,
過分滿足第一范式將為表建立太多的列
過分滿足第三范式將造成太多的表關(guān)聯(lián)
使用外鍵將帶來額外的開銷