一、常見存儲(chǔ)引擎1.1 InnoDB InnoDB 是 MySQL 5.5 之后默認(rèn)的存儲(chǔ)引擎,它具有高可靠、高性能的特點(diǎn),主要具備以下優(yōu)勢(shì):
一個(gè) InnoDB 引擎完整的內(nèi)存結(jié)構(gòu)和磁盤結(jié)構(gòu)如下圖所示: 1.2 MyISAM MyISAM 是 MySQL 5.5 之前默認(rèn)的存儲(chǔ)引擎。創(chuàng)建 MyISAM 表時(shí)會(huì)創(chuàng)建兩個(gè)同名的文件:
在 MySQL 8.0 之后,只會(huì)創(chuàng)建上述兩個(gè)同名文件,因?yàn)?8.0 后表結(jié)構(gòu)的定義存儲(chǔ)在 MySQL 數(shù)據(jù)字典中,但在 MySQL 8.0 之前,還會(huì)存在一個(gè)擴(kuò)展名為 .frm 的文件,用于存儲(chǔ)表結(jié)構(gòu)信息。MyISAM 與 InnoDB 主要的區(qū)別其只支持表級(jí)鎖,不支持行級(jí)鎖,不支持事務(wù),不支持自動(dòng)崩潰恢復(fù),但可以使用內(nèi)置的 mysqlcheck 和 myisamchk 工具來進(jìn)行檢查和修復(fù)。 1.3 MEMORYMEMORY 存儲(chǔ)引擎(又稱為 HEAP 存儲(chǔ)引擎)通常用于將表中的數(shù)據(jù)存儲(chǔ)在內(nèi)存中,它具有以下特征:
基于以上特性,MEMORY 表主要適合于存儲(chǔ)臨時(shí)數(shù)據(jù) ,如會(huì)話狀態(tài)、實(shí)時(shí)位置等信息。 1.4 CSVCSV 存儲(chǔ)引擎使用逗號(hào)分隔值的格式將數(shù)據(jù)存儲(chǔ)在文本文件中。創(chuàng)建 CSV 表時(shí)會(huì)同時(shí)創(chuàng)建兩個(gè)同名的文件:
ARCHIVE 存儲(chǔ)引擎默認(rèn)采用 zlib 無損數(shù)據(jù)壓縮算法進(jìn)行數(shù)據(jù)壓縮,能夠利用極小的空間存儲(chǔ)大量的數(shù)據(jù)。創(chuàng)建ARCHIVE 表時(shí),存儲(chǔ)引擎會(huì)創(chuàng)建與表同名的 ARZ 文件,用于存儲(chǔ)數(shù)據(jù)。它還具有以下特點(diǎn):
MERGE 存儲(chǔ)引擎,也稱為 MRG_MyISAM 引擎,是一組相同 MyISAM 表的集合。 ”相同” 表示所有表必須具有相同的列數(shù)據(jù)類型和索引信息??梢酝ㄟ^ UNION = (list-of-tables) 選項(xiàng)來創(chuàng)建 MERGE 表,如下: mysql> CREATE TABLE t1 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, message CHAR(20)) ENGINE=MyISAM;mysql> CREATE TABLE t2 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, message CHAR(20)) ENGINE=MyISAM;mysql> INSERT INTO t1 (message) VALUES ('Testing'),('table'),('t1');mysql> INSERT INTO t2 (message) VALUES ('Testing'),('table'),('t2');mysql> CREATE TABLE total (a INT NOT NULL AUTO_INCREMENT,message CHAR(20), INDEX(a)) ENGINE=MERGE UNION=(t1,t2) INSERT_METHOD=LAST;復(fù)制代碼 創(chuàng)建表時(shí)可以通過 INSERT_METHOD 選項(xiàng)來控制 MERGE 表的插入:使用 FIRST 或 LAST 分別表示在第一個(gè)或最后一個(gè)基礎(chǔ)表中進(jìn)行插入;如果未指定 INSERT_METHOD 或者設(shè)置值為 NO ,則表示不允許在 MERGE 表上執(zhí)行插入操作。MERGE 表支持 SELECT,DELETE,UPDATE 和 DELETE 語句,示例如下: 二、索引2.1 B+ tree 數(shù)據(jù)結(jié)構(gòu)如果沒有特殊說明,通常大多數(shù)數(shù)據(jù)庫采用的索引都是 B+ tree 索引,它是基于 B+ tree 這種數(shù)據(jù)結(jié)構(gòu)構(gòu)建的。為什么采用 B+ tree 而不是平衡二叉樹 (AVL) 或紅黑樹等數(shù)據(jù)結(jié)構(gòu)?這里假設(shè)索引為 1-16 的自增數(shù)據(jù),各類數(shù)據(jù)結(jié)構(gòu)的表現(xiàn)如下: 平衡二叉樹數(shù)據(jù)結(jié)構(gòu): 紅黑樹數(shù)據(jù)結(jié)構(gòu): Btree 數(shù)據(jù)結(jié)構(gòu): B+ Tree 數(shù)據(jù)結(jié)構(gòu)
從上面的圖示中我們可以看出 B+ Tree 樹具有以下優(yōu)點(diǎn):
對(duì)于 InnoDB ,因?yàn)橹麈I索引是聚集索引,所以其葉子節(jié)點(diǎn)存儲(chǔ)的就是實(shí)際的數(shù)據(jù)。而非主鍵索引存儲(chǔ)的則是主鍵的值 : 對(duì)于 MyISAM,因?yàn)橹麈I索引是非聚集索引,所以其葉子節(jié)點(diǎn)存儲(chǔ)的只是指向數(shù)據(jù)位置的指針: 綜上所述,B+ tree 結(jié)構(gòu)普遍適用于范圍查找,優(yōu)化排序和分組等操作。B+ tree 是基于字典序進(jìn)行構(gòu)建的,因此其適用于以下查詢:
使用哈希索引時(shí),存儲(chǔ)引擎會(huì)對(duì)索引列的值進(jìn)行哈希運(yùn)算,并將計(jì)算出的哈希值和指向該行數(shù)據(jù)的指針存儲(chǔ)在索引中,因此它更適用于等值比較查詢,而不是范圍查詢,同樣也不能用于優(yōu)化排序和分組等操作。在建立哈希索引時(shí),需要選取選擇性比較高的列,即列上的數(shù)據(jù)不容易重復(fù) (如身份證號(hào)),這樣可以盡量避免哈希沖突。因?yàn)楣K饕⒉恍枰鎯?chǔ)索引列的數(shù)據(jù),所以其結(jié)構(gòu)比較緊湊,對(duì)應(yīng)的查詢速度也比較快。 InnoDB 引擎有一個(gè)名為 “自適應(yīng)哈希索引 (adaptive hash index)” 的功能,當(dāng)某些索引值被頻繁使用時(shí),它會(huì)在內(nèi)存中基于 B+ tree 索引再創(chuàng)建一個(gè)哈希索引,從而讓 B-Tree 索引具備哈希索引快速查找的優(yōu)點(diǎn)。 2.4 索引的優(yōu)點(diǎn)
InnoDB 存儲(chǔ)引擎支持以下兩種標(biāo)準(zhǔn)的行級(jí)鎖:
排它鎖和共享鎖的兼容情況如下: X X X 不兼容 不兼容 S 不兼容 兼容 3.2 意向共享鎖與意向排它鎖為了說明意向鎖的作用,這里先引入一個(gè)案例:假設(shè)事務(wù) A 利用 S 鎖鎖住了表中的某一行,讓其只能讀不能寫。之后事務(wù) B 嘗試申請(qǐng)整個(gè)表的寫鎖,如果事務(wù) B 申請(qǐng)成功,那么理論上它就應(yīng)該能修改表中的任意一行,這與事務(wù) A 持有的行鎖是沖突的。想要解決這個(gè)問題,數(shù)據(jù)庫必須知道表中某一行已經(jīng)被鎖定,從而在事務(wù) B 嘗試申請(qǐng)整個(gè)表的寫鎖時(shí)阻塞它。想要知道表中某一行被鎖定,可以對(duì)表的每一行進(jìn)行遍歷,這種方式可行但是性能比較差,所以 InnoDB 引入了意向鎖。
按照意向鎖的規(guī)則,當(dāng)上面的事務(wù) A 給表中的某一行加 S 鎖時(shí),會(huì)同時(shí)給表加上 IS 鎖,之后事務(wù) B 嘗試獲取表的 X 鎖時(shí),由于 X 鎖與 IS 鎖并不兼容,所以事務(wù) B 會(huì)被阻塞。 X IX S IS X 不兼容 不兼容 不兼容 不兼容 IX 不兼容 兼容 不兼容 兼容 S 不兼容 不兼容 兼容 兼容 IS 不兼容 兼容 兼容 兼容 3.3 一致性讀1. 一致性非鎖定讀 一致非鎖定讀 (consistent nonlocking read) 是指在 InnoDB 存儲(chǔ)引擎下,如果將要讀取的行正在執(zhí)行 DELETE 或 UPDATE 操作,此時(shí)不必去等待行上鎖的釋放,而是去讀取 undo 日志上該行的快照數(shù)據(jù),具體如下:
基于多版本并發(fā)控制和一致性非鎖定讀,可以避免獲取鎖的等待,從而提高并發(fā)訪問下的性能。 2. 一致性鎖定度 一致性鎖定讀則允許用戶按照自己的需求在進(jìn)行 SELECT 操作時(shí)手動(dòng)加鎖,通常有以下兩種方式:
InnoDB 存儲(chǔ)引擎支持以下三種鎖的算法: Record Lock:行鎖,用于鎖定單個(gè)行記錄。示例如下: -- 利用行鎖可以防止其他事務(wù)更新或刪除該行SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;復(fù)制代碼 Gap Lock:間隙鎖,鎖定一個(gè)范圍,但不包括記錄本身,主要用于解決幻讀問題,示例如下:
Next-Key Lock:等價(jià)于 行鎖+間隙鎖,既鎖定范圍,也鎖定記錄本身。可以用于解決幻讀中的 ”當(dāng)前讀“ 的問題。 四、事務(wù)4.1 ACID 定義InnoDB 存儲(chǔ)引擎完全支持 ACID 模型: 1. 原子性(Atomicity) 事務(wù)是不可分割的最小工作單元,事務(wù)的所有操作要么全部提交成功,要么全部失敗回滾,不存在部分成功的情況。 2. 一致性(Consistency) 數(shù)據(jù)庫在事務(wù)執(zhí)行前后都保持一致性狀態(tài),數(shù)據(jù)庫的完整性沒有被破壞。 3. 隔離性(Isolation) 允許多個(gè)并發(fā)事務(wù)同時(shí)對(duì)數(shù)據(jù)進(jìn)行操作,但一個(gè)事務(wù)所做的修改在最終提交以前,對(duì)其它事務(wù)是不可見的。 4. 持久性(Durability) 一旦事務(wù)提交,則其所做的修改將會(huì)永遠(yuǎn)保存到數(shù)據(jù)庫中。即使宕機(jī)等故障,也不會(huì)丟失。 4.2 事務(wù)的實(shí)現(xiàn)數(shù)據(jù)庫隔離性由上一部分介紹的鎖來實(shí)現(xiàn),而原子性、一致性、持久性都由 undo log 和 redo log 來實(shí)現(xiàn)。
在并發(fā)環(huán)境下,數(shù)據(jù)的更改通常會(huì)產(chǎn)生下面四種問題: 1.丟失更新 一個(gè)事務(wù)的更新操作被另外一個(gè)事務(wù)的更新操作鎖覆蓋,從而導(dǎo)致數(shù)據(jù)不一致: 2. 臟讀 在不同的事務(wù)下,一個(gè)事務(wù)讀取到其他事務(wù)未提交的數(shù)據(jù): 3. 不可重復(fù)讀 在同一個(gè)事務(wù)的兩次讀取之間,由于其他事務(wù)對(duì)數(shù)據(jù)進(jìn)行了修改,導(dǎo)致對(duì)同一條數(shù)據(jù)兩次讀到的結(jié)果不一致: 4.幻讀 在同一個(gè)事務(wù)的兩次讀取之間,由于其他事務(wù)對(duì)數(shù)據(jù)進(jìn)行了修改,導(dǎo)致第二次讀取到第一次不存在數(shù)據(jù),或第一次原本存在的數(shù)據(jù),第二次卻讀取不到,就好像之前的讀取是 “幻覺” 一樣: 4.4 隔離級(jí)別 想要解決以上問題,可以通過設(shè)置隔離級(jí)別來實(shí)現(xiàn):InnoDB 支持以下四個(gè)等級(jí)的隔離級(jí)別,默認(rèn)隔離級(jí)別為可重復(fù)讀:
在每個(gè)級(jí)別下,并發(fā)問題是否可能出現(xiàn)的情況如下: 隔離級(jí)別 臟讀 不可重復(fù)讀 幻讀 讀未提交(READ UNCOMMITTED) 可能出現(xiàn) 可能 可能 讀已提交(READ COMMITTED) 不可能出現(xiàn) 可能 可能 可重復(fù)讀(REPEATABLE READ) 不可能 不可能 可能 串行化(SERIALIZABLE) 不可能 不可能 不可能 就數(shù)據(jù)庫層面而言,當(dāng)前任何隔離級(jí)別下都不會(huì)發(fā)生丟失更新的問題,以 InnoDB 存儲(chǔ)引擎為例,如果你想要更改表中某行數(shù)據(jù),該行數(shù)據(jù)上必然會(huì)加上 X 鎖,而對(duì)應(yīng)的表上則會(huì)加上 IX 鎖,其他任何事務(wù)必須等待獲取該鎖才能進(jìn)行修改操作。 五、數(shù)據(jù)庫設(shè)計(jì)范式數(shù)據(jù)庫設(shè)計(jì)當(dāng)中常用的三范式如下: 第一范式:屬性不可分要求表中的每一列都是不可再細(xì)分的原子項(xiàng)。這是最低的范式要求,通常都能夠被滿足。 第二范式:屬性完全依賴于主鍵要求非主鍵列必須完全依賴于主鍵列,而不能存在部分依賴。示例如下: mechanism_id (組織機(jī)構(gòu)代碼) employee_id (雇員編號(hào)) ename (雇員名稱) mname (機(jī)構(gòu)名稱) 28193182 10001 heibaiying XXXX公司 以上是一張全市在職人員統(tǒng)計(jì)表,主鍵為:機(jī)構(gòu)編碼 + 雇員編號(hào)。表中的雇員名稱完全依賴于此聯(lián)合主鍵,但機(jī)構(gòu)名稱卻只依賴于機(jī)構(gòu)編碼,這就是部分依賴,因此違背了第二范式。此時(shí)常用的解決方式是建立一張組織機(jī)構(gòu)與組織名稱的字典表。 第三范式:避免傳遞依賴非主鍵列不能依賴于其他非主鍵列,如果其他非主鍵列又依賴于主鍵列,此時(shí)就出現(xiàn)了傳遞依賴。示例如下: employee_id (雇員編號(hào)) ename (雇員名稱) dept_no (部門編號(hào)) dname(部門名稱) 10001 heibaiying 06 開發(fā)部 以上是一張雇員表,雇員名稱和所屬的部門編號(hào)都依賴于主鍵 employee_id ,但部門名稱卻依賴于部門編號(hào),此時(shí)就出現(xiàn)了非主鍵列依賴于其他非主鍵列,這就違背的第三范式。此時(shí)常用的解決方式是建立一張部門表用于維護(hù)部門相關(guān)的信息。 反范式設(shè)計(jì)從上面的例子中我們也可以看出,想要完全遵循三范式設(shè)計(jì),可能需要額外增加很多表來進(jìn)行維護(hù)。所以在日常開發(fā)中,基于其他因素的綜合考量,可能并不會(huì)完全遵循范式設(shè)計(jì),甚至可能違反范式設(shè)計(jì),這就是反范式設(shè)計(jì)。 |
|