数据库技术支持文档
數(shù)據(jù)庫(kù)技術(shù)支持文檔
說(shuō)明
對(duì)平時(shí)工作學(xué)習(xí)遇到的數(shù)據(jù)庫(kù)相關(guān)知識(shí)和技巧記錄,會(huì)對(duì)一些優(yōu)秀知識(shí)講解文章的摘錄,包括PostgreSQL、MySQL、Oracle等
| 1.0 | 初稿 | 2021-05-28 | pitt1997 |
完整文件下載
數(shù)據(jù)庫(kù)技術(shù)支持文檔.pdf
數(shù)據(jù)庫(kù)技術(shù)支持文檔.md
MySQL
MySQL 數(shù)據(jù)怎么存儲(chǔ)?MySQL 中的數(shù)據(jù)在磁盤(pán)上,它到底是如何進(jìn)行存儲(chǔ)的?長(zhǎng)什么樣?
掃盲:存儲(chǔ)引擎是作用在表上的。
主要命令
查詢(xún)當(dāng)前數(shù)據(jù)庫(kù)支持的存儲(chǔ)引擎
mysql> show engines;查詢(xún)當(dāng)前默認(rèn)的存儲(chǔ)引擎
mysql> show variables like '%storage_engine%';查詢(xún)表的相關(guān)信息(先使用具體的數(shù)據(jù)庫(kù))
use '數(shù)據(jù)庫(kù)名'; show table status like '表名';查看是否開(kāi)啟了binlog
mysql> show variables like 'log_%';查看指定binlog文件的內(nèi)容
mysql> show binlog events in 'mysql-bin.000001';只查看第一個(gè)binlog文件的內(nèi)容
mysql> show binlog events;儲(chǔ)存引擎
MySQL 中的數(shù)據(jù)用各種不同的技術(shù)存儲(chǔ)在文件(或者內(nèi)存)中,這些不同的技術(shù)以及配套的相關(guān)功能在MySQL 中被稱(chēng)作存儲(chǔ)引擎。不同的存儲(chǔ)引擎,我們的數(shù)據(jù)存儲(chǔ)的格式也會(huì)不一樣。
MySQL 中常用的存儲(chǔ)引擎有兩種:MyISAM 和 InnoDB。并且存儲(chǔ)引擎是作用在表上的!
MySQL 5.5之前,MyISAM 是默認(rèn)的存儲(chǔ)引擎。MySQL 5.5開(kāi)始,InnoDB 是默認(rèn)的存儲(chǔ)引擎。
主要區(qū)別
| 事務(wù) | 不支持 | 支持 |
| 表/行鎖 | 只有表鎖 | 還引入了行鎖 |
| 外鍵 | 不支持 | 支持 |
| 全文索引 | 支持 | 版本5.6 開(kāi)始支持 |
| 讀寫(xiě)速度 | 更快 | 更慢 |
相關(guān)命令
查詢(xún)當(dāng)前數(shù)據(jù)庫(kù)支持的存儲(chǔ)引擎
mysql> show engines;[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-kliRhbUm-1629818634013)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1622134580802.png)]
查詢(xún)當(dāng)前默認(rèn)的存儲(chǔ)引擎
mysql> show variables like '%storage_engine%';[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-7L2KTRxp-1629818634015)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1622134667543.png)]
查詢(xún)表的相關(guān)信息(先使用具體的數(shù)據(jù)庫(kù))
use '數(shù)據(jù)庫(kù)名'; show table status like '表名';[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-xp2RZo7F-1629818634016)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1622135393028.png)]
查詢(xún)說(shuō)明詳細(xì)資料參考鏈接:鏈接
| name | 表名 |
| Engine | 存儲(chǔ)引擎 |
| Version | 版本 |
| Row_format | 行格式 |
| … | … |
MyISAM
每個(gè) MyISAM 表都以3個(gè)文件存儲(chǔ)在磁盤(pán)上。這些文件的名稱(chēng)以表名開(kāi)頭,以擴(kuò)展名指示文件類(lèi)型。
.frm 文件(frame)存儲(chǔ)表結(jié)構(gòu);
.MYD 文件(MY Data)存儲(chǔ)表數(shù)據(jù);
.MYI 文件(MY Index)存儲(chǔ)表索引。
MySQL 里的數(shù)據(jù)默認(rèn)是存放在安裝目錄下的 data 文件夾中,也可以自己修改。
MyISAM中的B+tree
.MYI 文件組織索引的方式就是 B+tree。葉子節(jié)點(diǎn)的 value 處存放的就是索引所在行的磁盤(pán)文件地址。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-FBKi2oXy-1629818634019)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1622982461610.png)]
底層查找過(guò)程
首先會(huì)判斷查找條件 where 中的字段是否是索引字段,如果是就會(huì)先拿著這字段去 .MYI 文件里通過(guò) B+tree 快速定位,從根節(jié)點(diǎn)開(kāi)始定位查找;
找到后再把這個(gè)索引關(guān)鍵字(就是我們的條件)存放的磁盤(pán)文件地址拿到 .MYD 文件里面找,從而定位到索引所在行的記錄。
注意:表邏輯上相鄰的記錄行數(shù)據(jù)在磁盤(pán)上并不一定是物理相鄰的。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-XGnjKLCx-1629818634021)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1622982415598.png)]
InnoDB
一張 InnoDB 表底層會(huì)對(duì)應(yīng)2個(gè)文件在文件夾中進(jìn)行數(shù)據(jù)存儲(chǔ)。
.frm 文件(frame)存儲(chǔ)表結(jié)構(gòu);
.ibd 文件(InnoDB Data)存儲(chǔ)表索引+數(shù)據(jù)。
下面我創(chuàng)建了以 InnoDB 作為存儲(chǔ)引擎的一張表 t_user_innodb。
很顯然,InnoDB 把索引和數(shù)據(jù)都放在一個(gè)文件里存著了。毫無(wú)疑問(wèn),InnoDB 表里面的數(shù)據(jù)也是用 B+tree 數(shù)據(jù)結(jié)構(gòu)組織起來(lái)的。
下面我們來(lái)看看它具體是怎么存儲(chǔ)的。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-ggQ1MkMW-1629818634022)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1622982609363.png)]
.ibd 存儲(chǔ)數(shù)據(jù)的特點(diǎn)就是 B+tree 的葉子節(jié)點(diǎn)上包括了我們要的索引和該索引所在行的其它列數(shù)據(jù)。
底層查找過(guò)程:
首先會(huì)判斷查找條件 where 中的字段是否是索引字段,如果是就會(huì)先拿著這字段去 .ibd 文件里通過(guò) B+tree 快速定位,從根節(jié)點(diǎn)開(kāi)始定位查找;
找到后直接把這個(gè)索引關(guān)鍵字及其記錄所在行的其它列數(shù)據(jù)返回。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-QMwllPlA-1629818634023)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1622982673790.png)]
聚集(聚簇)索引
聚集索引:葉子節(jié)點(diǎn)包含了完整的數(shù)據(jù)記錄。
簡(jiǎn)單來(lái)說(shuō)就是索引和它所在行的其它列數(shù)據(jù)全部都在一起了。
很顯然,MyISAM 沒(méi)有聚集索引,InnoDB 有,而且 InnoDB 的主鍵索引就是天然的聚集索引。
有聚集索引當(dāng)然就有非聚集索引(稀疏索引)。對(duì)于 MyISAM 來(lái)說(shuō),它的索引就是非聚集索引。因?yàn)樗?strong>索引和數(shù)據(jù)是分開(kāi)兩個(gè)文件存的:一個(gè) .MYI 存索引,一個(gè) .MYD 存數(shù)據(jù)。
為什么要有主鍵?
為什么 DBA 都建議表中一定要有主鍵,而且推薦使用整型自增?
因?yàn)?InnoDB 表里面的數(shù)據(jù)必須要有一個(gè) B+tree 的索引結(jié)構(gòu)來(lái)組織、維護(hù)我們的整張表的所有數(shù)據(jù),從而形成 .idb 文件。
那和主鍵有什么關(guān)系?
如果 InnoDB 創(chuàng)建了一張沒(méi)有主鍵的表,那這張表就有可能沒(méi)有任何索引,則 MySQL會(huì)選擇所有具有唯一性并且不為 null 中的第一個(gè)字段的創(chuàng)建聚集索引。
如果沒(méi)有唯一性索引的字段就會(huì)有一個(gè)隱式字段成為表的聚集索引:而這個(gè)隱式字段,就是 InnoDB 幫我們創(chuàng)建的一個(gè)長(zhǎng)度為 6字節(jié) 的整數(shù)列 ROW_ID,它隨著新行的插入單調(diào)增加,InnoDB 就以該列對(duì)數(shù)據(jù)進(jìn)行聚集。
使用這個(gè) ROW_ID 列的表都共享一個(gè)相同的全局序列計(jì)數(shù)器(這是數(shù)據(jù)字典的一部分)。為了避免這個(gè) ROW_ID 用完,所以建議表中一定要單獨(dú)建立一個(gè)主鍵字段。
為什么推薦使用整型自增?
首先整型的占用空間會(huì)比字符串小,而且在查找上比大小也會(huì)比字符串更快。字符串比大小的時(shí)候還要先轉(zhuǎn)換成 ASCII 碼再去比較。
如果使用自增的話(huà),在插入方面的效率也會(huì)提高。
不使用自增,可能時(shí)不時(shí)會(huì)往 B+tree 的中間某一位置插入元素,當(dāng)這個(gè)節(jié)點(diǎn)位置放滿(mǎn)了的時(shí)候,節(jié)點(diǎn)就要進(jìn)行分裂操作(效率低)再去維護(hù),有可能樹(shù)還要進(jìn)行平衡,又是一個(gè)耗性能的操作。
都用自增就會(huì)永遠(yuǎn)都往后面插入元素,這樣索引節(jié)點(diǎn)分裂的概率就會(huì)小很多。
MySQL的一級(jí)索引和二級(jí)索引
一級(jí)索引和二級(jí)索引即對(duì)應(yīng)聚集索引和非聚集索引,葉子節(jié)點(diǎn)存放主索引和數(shù)據(jù)的樹(shù),稱(chēng)為聚集索引樹(shù);葉子節(jié)點(diǎn)存放輔助索引和主索引的樹(shù),稱(chēng)為非聚集索引樹(shù)。
一級(jí)索引(聚集索引)
索引和數(shù)據(jù)存儲(chǔ)在一起,都存儲(chǔ)在同一個(gè)B+tree中的葉子節(jié)點(diǎn)。一般主鍵索引都是一級(jí)索引
二級(jí)索引(非聚集索引)
除聚集索引之外的所有索引都叫做二級(jí)索引,也稱(chēng)輔助索引。
它的葉子節(jié)點(diǎn)則不會(huì)存儲(chǔ)其它所有列的數(shù)據(jù),就只存儲(chǔ)主鍵值。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-ahqUqbhF-1629818634025)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1622982870747.png)]
底層查找過(guò)程:
每次要找數(shù)據(jù)的時(shí)候,會(huì)根據(jù)它找到對(duì)應(yīng)葉子節(jié)點(diǎn)的主鍵值,再把它拿到聚集索引的 B+tree 中查找,從而拿到整條記錄。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-RqilMesM-1629818634025)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1622982895238.png)]
優(yōu)點(diǎn):保持一致性和節(jié)省空間。
SQL執(zhí)行過(guò)程
我們的系統(tǒng)到底是如何和 MySQL 交互的?MySQL 如何幫我們存儲(chǔ)數(shù)據(jù)、又是如何幫我們管理事務(wù)?MySQL 在接受到我們發(fā)送的 SQL 語(yǔ)句時(shí)又分別做了哪些事情?
MySQL 驅(qū)動(dòng)
我們的系統(tǒng)在和 MySQL 數(shù)據(jù)庫(kù)進(jìn)行通信的時(shí)候,總不可能是平白無(wú)故的就能接收和發(fā)送請(qǐng)求,就算是你沒(méi)有做什么操作,那總該是有其他的“人”幫我們做了一些事情,基本上使用過(guò) MySQL 數(shù)據(jù)庫(kù)的程序員多多少少都會(huì)知道 MySQL 驅(qū)動(dòng)這個(gè)概念的。就是這個(gè) MySQL 驅(qū)動(dòng)在底層幫我們做了對(duì)數(shù)據(jù)庫(kù)的連接,只有建立了連接了,才能夠有后面的交互
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-P3WEIzae-1629818634027)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1623083908291.png)]
這樣的話(huà),在系統(tǒng)和 MySQL 進(jìn)行交互之前,MySQL 驅(qū)動(dòng)會(huì)幫我們建立好連接,然后我們只需要發(fā)送 SQL 語(yǔ)句就可以執(zhí)行 CRUD 了。一次 SQL 請(qǐng)求就會(huì)建立一個(gè)連接,多個(gè)請(qǐng)求就會(huì)建立多個(gè)連接,那么問(wèn)題來(lái)了,我們系統(tǒng)肯定不是一個(gè)人在使用的,換句話(huà)說(shuō)肯定是存在多個(gè)請(qǐng)求同時(shí)去爭(zhēng)搶連接的情況。我們的 web 系統(tǒng)一般都是部署在 tomcat 容器中的,而 tomcat 是可以并發(fā)處理多個(gè)請(qǐng)求的,這就會(huì)導(dǎo)致多個(gè)請(qǐng)求會(huì)去建立多個(gè)連接,然后使用完再都去關(guān)閉,這樣會(huì)有什么問(wèn)題呢?
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-cEk7yVmm-1629818634027)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1623084123562.png)]
Java系統(tǒng)在通過(guò) MySQL 驅(qū)動(dòng)和 MySQL 數(shù)據(jù)庫(kù)連接的時(shí)候是基于 TCP/IP 協(xié)議的,所以如果每個(gè)請(qǐng)求都是新建連接和銷(xiāo)毀連接,那這樣勢(shì)必會(huì)造成不必要的浪費(fèi)和性能的下降,也就說(shuō)上面的多線(xiàn)程請(qǐng)求的時(shí)候頻繁的創(chuàng)建和銷(xiāo)毀連接顯然是不合理的。必然會(huì)大大降低我們系統(tǒng)的性能,但是如果給你提供一些固定的用來(lái)連接的線(xiàn)程,這樣是不是不需要反復(fù)的創(chuàng)建和銷(xiāo)毀連接了呢?相信懂行的朋友會(huì)會(huì)心一笑,沒(méi)錯(cuò),說(shuō)的就是數(shù)據(jù)庫(kù)連接池。
數(shù)據(jù)庫(kù)連接池:維護(hù)一定的連接數(shù),方便系統(tǒng)獲取連接,使用就去池子中獲取,用完放回去就可以了,我們不需要關(guān)心連接的創(chuàng)建與銷(xiāo)毀,也不需要關(guān)心線(xiàn)程池是怎么去維護(hù)這些連接的。[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-2wvUHpYK-1629818634029)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1623084190998.png)]
常見(jiàn)的數(shù)據(jù)庫(kù)連接池有 Druid、C3P0、DBCP,連接池實(shí)現(xiàn)原理在這里就不深入討論了,采用連接池大大節(jié)省了不斷創(chuàng)建與銷(xiāo)毀線(xiàn)程的開(kāi)銷(xiāo),這就是有名的「池化」思想,不管是線(xiàn)程池還是 HTTP 連接池,都能看到它的身影。數(shù)據(jù)庫(kù)連接池
到這里,我們已經(jīng)知道的是我們的系統(tǒng)在訪(fǎng)問(wèn) MySQL 數(shù)據(jù)庫(kù)的時(shí)候,建立的連接并不是每次請(qǐng)求都會(huì)去創(chuàng)建的,而是從數(shù)據(jù)庫(kù)連接池中去獲取,這樣就解決了因?yàn)榉磸?fù)的創(chuàng)建和銷(xiāo)毀連接而帶來(lái)的性能損耗問(wèn)題了。不過(guò)這里有個(gè)小問(wèn)題,業(yè)務(wù)系統(tǒng)是并發(fā)的,而 MySQL 接受請(qǐng)求的線(xiàn)程呢,只有一個(gè)?
其實(shí)MySQL 的架構(gòu)體系中也已經(jīng)提供了這樣的一個(gè)池子,也是數(shù)據(jù)庫(kù)連池。雙方都是通過(guò)數(shù)據(jù)庫(kù)連接池來(lái)管理各個(gè)連接的,這樣一方面線(xiàn)程之前不需要是爭(zhēng)搶連接,更重要的是不需要反復(fù)的創(chuàng)建的銷(xiāo)毀連接。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-tBHsB7VA-1629818634030)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1623084436096.png)]
至此系統(tǒng)和 MySQL 數(shù)據(jù)庫(kù)之間的連接問(wèn)題已經(jīng)說(shuō)明清楚了。那么 MySQL 數(shù)據(jù)庫(kù)中的這些連接是怎么來(lái)處理的,又是誰(shuí)來(lái)處理呢?
網(wǎng)絡(luò)連接必須由線(xiàn)程來(lái)處理
對(duì)計(jì)算基礎(chǔ)稍微有一點(diǎn)了解的的同學(xué)都是知道的,網(wǎng)絡(luò)中的連接都是由線(xiàn)程來(lái)處理的,所謂網(wǎng)絡(luò)連接說(shuō)白了就是一次請(qǐng)求,每次請(qǐng)求都會(huì)有相應(yīng)的線(xiàn)程去處理的。也就是說(shuō)對(duì)于 SQL 語(yǔ)句的請(qǐng)求在 MySQL 中是由一個(gè)個(gè)的線(xiàn)程去處理的。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-HyshhPSC-1629818634031)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1623084473459.png)]
那這些線(xiàn)程會(huì)怎么去處理這些請(qǐng)求?會(huì)做哪些事情?
SQL 接口
MySQL 中處理請(qǐng)求的線(xiàn)程在獲取到請(qǐng)求以后獲取 SQL 語(yǔ)句去交給 SQL 接口去處理。
查詢(xún)解析器
假如現(xiàn)在有這樣的一個(gè) SQL
SELECT stuName,age,sex FROM students WHERE id = 1但是這個(gè) SQL 是寫(xiě)給我們?nèi)丝吹?#xff0c;機(jī)器哪里知道你在說(shuō)什么?這個(gè)時(shí)候解析器就上場(chǎng)了。他會(huì)將 SQL 接口傳遞過(guò)來(lái)的 SQL 語(yǔ)句進(jìn)行解析,翻譯成 MySQL 自己能認(rèn)識(shí)的語(yǔ)言,至于怎么解析的就不需要在深究了,無(wú)非是自己一套相關(guān)的規(guī)則。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-3E0og3jl-1629818634032)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1625379739838.png)]
現(xiàn)在 SQL 已經(jīng)被解析成 MySQL 認(rèn)識(shí)的樣子的,那下一步是不是就是執(zhí)行嗎?理論上是這樣子的,但是 MySQL 的強(qiáng)大遠(yuǎn)不止于此,他還會(huì)幫我們選擇最優(yōu)的查詢(xún)路徑。
什么叫最優(yōu)查詢(xún)路徑?就是 MySQL 會(huì)按照自己認(rèn)為的效率最高的方式去執(zhí)行查詢(xún)
具體是怎么做到的呢?這就要說(shuō)到 MySQL 的查詢(xún)優(yōu)化器了
MySQL 查詢(xún)優(yōu)化器
查詢(xún)優(yōu)化器內(nèi)部具體怎么實(shí)現(xiàn)的我們不需要是關(guān)心,我需要知道的是 MySQL 會(huì)幫我去使用他自己認(rèn)為的最好的方式去優(yōu)化這條 SQL 語(yǔ)句,并生成一條條的執(zhí)行計(jì)劃,比如你創(chuàng)建了多個(gè)索引,MySQL 會(huì)依據(jù)成本最小原則來(lái)選擇使用對(duì)應(yīng)的索引,這里的成本主要包括兩個(gè)方面, IO 成本和 CPU 成本
IO 成本: 即從磁盤(pán)把數(shù)據(jù)加載到內(nèi)存的成本,默認(rèn)情況下,讀取數(shù)據(jù)頁(yè)的 IO 成本是 1,MySQL 是以頁(yè)的形式讀取數(shù)據(jù)的,即當(dāng)用到某個(gè)數(shù)據(jù)時(shí),并不會(huì)只讀取這個(gè)數(shù)據(jù),而會(huì)把這個(gè)數(shù)據(jù)相鄰的數(shù)據(jù)也一起讀到內(nèi)存中,這就是有名的程序局部性原理,所以 MySQL 每次會(huì)讀取一整頁(yè),一頁(yè)的成本就是 1。所以 IO 的成本主要和頁(yè)的大小有關(guān)
CPU 成本:將數(shù)據(jù)讀入內(nèi)存后,還要檢測(cè)數(shù)據(jù)是否滿(mǎn)足條件和排序等 CPU 操作的成本,顯然它與行數(shù)有關(guān),默認(rèn)情況下,檢測(cè)記錄的成本是 0.2。
MySQL 優(yōu)化器 會(huì)計(jì)算 「IO 成本 + CPU」 成本最小的那個(gè)索引來(lái)執(zhí)行
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-em8KBQZm-1629818634034)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1625379842659.png)]
優(yōu)化器執(zhí)行選出最優(yōu)索引等步驟后,會(huì)去調(diào)用【存儲(chǔ)引擎接口】,【存儲(chǔ)引擎接口】需要由【執(zhí)行器】進(jìn)行調(diào)用,開(kāi)始去執(zhí)行被 MySQL 解析過(guò)和優(yōu)化過(guò)的 SQL 語(yǔ)句
執(zhí)行器
執(zhí)行器是一個(gè)非常重要的組件,因?yàn)榍懊婺切┙M件的操作最終必須通過(guò)執(zhí)行器去調(diào)用存儲(chǔ)引擎接口才能被執(zhí)行。執(zhí)行器最終最根據(jù)一系列的執(zhí)行計(jì)劃去調(diào)用存儲(chǔ)引擎的接口去完成 SQL 的執(zhí)行
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-GuIshQeo-1629818634035)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1625379965648.png)]
存儲(chǔ)引擎
查詢(xún)優(yōu)化器會(huì)調(diào)用存儲(chǔ)引擎的接口,去執(zhí)行 SQL,也就是說(shuō)真正執(zhí)行 SQL 的動(dòng)作是在存儲(chǔ)引擎中完成的。數(shù)據(jù)是被存放在內(nèi)存或者是磁盤(pán)中的(存儲(chǔ)引擎是一個(gè)非常重要的組件,開(kāi)頭也已經(jīng)介紹)
我們以一個(gè)更新的SQL語(yǔ)句來(lái)說(shuō)明,SQL 如下
UPDATE students SET stuName = '小強(qiáng)' WHERE id = 1當(dāng)我們系統(tǒng)發(fā)出這樣的查詢(xún)?nèi)ソ唤o MySQL 的時(shí)候,MySQL 會(huì)按照我們上面介紹的一系列的流程最終通過(guò)執(zhí)行器調(diào)用存儲(chǔ)引擎去執(zhí)行,流程圖就是上面那個(gè)。在執(zhí)行這個(gè) SQL 的時(shí)候 SQL 語(yǔ)句對(duì)應(yīng)的數(shù)據(jù)要么是在內(nèi)存中,要么是在磁盤(pán)中,如果直接在磁盤(pán)中操作,那這樣的隨機(jī)IO讀寫(xiě)的速度肯定讓人無(wú)法接受的,所以每次在執(zhí)行 SQL 的時(shí)候都會(huì)將其數(shù)據(jù)加載到內(nèi)存中,這塊內(nèi)存就是**【 InnoDB 】**中一個(gè)非常重要的組件:緩沖池 Buffer Pool
Buffer Pool (緩沖池)是 InnoDB 存儲(chǔ)引擎中非常重要的內(nèi)存結(jié)構(gòu),顧名思義,緩沖池其實(shí)就是類(lèi)似 Redis 一樣的作用,起到一個(gè)緩存的作用,因?yàn)槲覀兌贾?MySQL 的數(shù)據(jù)最終是存儲(chǔ)在磁盤(pán)中的,如果沒(méi)有這個(gè) Buffer Pool 那么我們每次的數(shù)據(jù)庫(kù)請(qǐng)求都會(huì)磁盤(pán)中查找,這樣必然會(huì)存在 IO 操作,這肯定是無(wú)法接受的。但是有了 Buffer Pool 就是我們第一次在查詢(xún)的時(shí)候會(huì)將查詢(xún)的結(jié)果存到 Buffer Pool 中,這樣后面再有請(qǐng)求的時(shí)候就會(huì)先從緩沖池中去查詢(xún),如果沒(méi)有再去磁盤(pán)中查找,然后在放到 Buffer Pool 中,如下圖
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-vmTzvXH2-1629818634037)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1625380312030.png)]
按照上面的那幅圖,這條 SQL 語(yǔ)句的執(zhí)行步驟大致是這樣子的
undo 日志文件:記錄數(shù)據(jù)被修改前的樣子
undo 顧名思義,就是沒(méi)有做,沒(méi)發(fā)生的意思。undo log 就是沒(méi)有發(fā)生事情(原本事情是什么)的一些日志
我們剛剛已經(jīng)說(shuō)了,在準(zhǔn)備更新一條語(yǔ)句的時(shí)候,該條語(yǔ)句已經(jīng)被加載到 Buffer pool 中了,實(shí)際上這里還有這樣的操作,就是在將該條語(yǔ)句加載到 Buffer Pool 中的時(shí)候同時(shí)會(huì)往 undo 日志文件中插入一條日志,也就是將 id=1 的這條記錄的原來(lái)的值記錄下來(lái)。
這樣做的目的是什么?
Innodb 存儲(chǔ)引擎的最大特點(diǎn)就是支持事務(wù),如果本次更新失敗,也就是事務(wù)提交失敗,那么該事務(wù)中的所有的操作都必須回滾到執(zhí)行前的樣子,也就是說(shuō)當(dāng)事務(wù)失敗的時(shí)候,也不會(huì)對(duì)原始數(shù)據(jù)有影響,看圖說(shuō)話(huà)
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-9vxD7qWf-1629818634038)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1625380409620.png)]
這里說(shuō)句額外話(huà),其實(shí) MySQL 也是一個(gè)系統(tǒng),就好比我們平時(shí)開(kāi)發(fā)的 java 的功能系統(tǒng)一樣,MySQL 使用的是自己相應(yīng)的語(yǔ)言開(kāi)發(fā)出來(lái)的一套系統(tǒng)而已,它根據(jù)自己需要的功能去設(shè)計(jì)對(duì)應(yīng)的功能,它即然能做到哪些事情,那么必然是設(shè)計(jì)者們當(dāng)初這么定義或者是根據(jù)實(shí)際的場(chǎng)景變更演化而來(lái)的。所以大家放平心態(tài),把 MySQL 當(dāng)作一個(gè)系統(tǒng)去了解熟悉他。
到這一步,我們的執(zhí)行的 SQL 語(yǔ)句已經(jīng)被加載到 Buffer Pool 中了,然后開(kāi)始更新這條語(yǔ)句,更新的操作實(shí)際是在Buffer Pool中執(zhí)行的,那問(wèn)題來(lái)了,按照我們平時(shí)開(kāi)發(fā)的一套理論緩沖池中的數(shù)據(jù)和數(shù)據(jù)庫(kù)中的數(shù)據(jù)不一致時(shí)候,我們就認(rèn)為緩存中的數(shù)據(jù)是臟數(shù)據(jù),**那此時(shí) Buffer Pool 中的數(shù)據(jù)豈不是成了臟數(shù)據(jù)?**沒(méi)錯(cuò),目前這條數(shù)據(jù)就是臟數(shù)據(jù),Buffer Pool 中的記錄是小強(qiáng) 數(shù)據(jù)庫(kù)中的記錄是旺財(cái) ,這種情況 MySQL是怎么處理的呢,繼續(xù)往下看
redo 日志文件:記錄數(shù)據(jù)被修改后的樣子
除了從磁盤(pán)中加載文件和將操作前的記錄保存到 undo 日志文件中,其他的操作是在內(nèi)存中完成的,內(nèi)存中的數(shù)據(jù)的特點(diǎn)就是:斷電丟失。如果此時(shí) MySQL 所在的服務(wù)器宕機(jī)了,那么 Buffer Pool 中的數(shù)據(jù)會(huì)全部丟失的。這個(gè)時(shí)候 redo 日志文件就需要來(lái)大顯神通了
注:redo 日志文件是 InnoDB 特有的,他是存儲(chǔ)引擎級(jí)別的,不是 MySQL 級(jí)別的redo 記錄的是數(shù)據(jù)修改之后的值,不管事務(wù)是否提交都會(huì)記錄下來(lái),例如,此時(shí)將要做的是update students set stuName='小強(qiáng)' where id=1; 那么這條操作就會(huì)被記錄到 redo log buffer 中,啥?怎么又出來(lái)一個(gè) redo log buffer ,很簡(jiǎn)單,MySQL 為了提高效率,所以將這些操作都先放在內(nèi)存中去完成,然后會(huì)在某個(gè)時(shí)機(jī)將其持久化到磁盤(pán)中。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-if2HOTmj-1629818634040)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1625380605315.png)]
截至目前,我們應(yīng)該都熟悉了 MySQL 的執(zhí)行器調(diào)用存儲(chǔ)引擎是怎么將一條 SQL 加載到緩沖池和記錄哪些日志的,流程如下:
上面說(shuō)的步驟都是在正常情況下的操作,但是程序的設(shè)計(jì)和優(yōu)化并不僅是為了這些正常情況而去做的,也是為了那些臨界區(qū)和極端情況下出現(xiàn)的問(wèn)題去優(yōu)化設(shè)計(jì)的
這個(gè)時(shí)候如果服務(wù)器宕機(jī)了,那么緩存中的數(shù)據(jù)還是丟失了。真煩,竟然數(shù)據(jù)總是丟失,那能不能不要放在內(nèi)存中,直接保存到磁盤(pán)呢?很顯然不行,因?yàn)樵谏厦嬉惨呀?jīng)介紹了,在內(nèi)存中的操作目的是為了提高效率。
此時(shí),如果 MySQL 真的宕機(jī)了,那么沒(méi)關(guān)系的,因?yàn)?MySQL 會(huì)認(rèn)為本次事務(wù)是失敗的,所以數(shù)據(jù)依舊是更新前的樣子,并不會(huì)有任何的影響。
好了,語(yǔ)句也更新好了那么需要將更新的值提交啊,也就是需要提交本次的事務(wù)了,因?yàn)橹灰聞?wù)成功提交了,才會(huì)將最后的變更保存到數(shù)據(jù)庫(kù),在提交事務(wù)前仍然會(huì)具有相關(guān)的其他操作
將 redo Log Buffer 中的數(shù)據(jù)持久化到磁盤(pán)中,就是將 redo log buffer 中的數(shù)據(jù)寫(xiě)入到 redo log 磁盤(pán)文件中,一般情況下,redo log Buffer 數(shù)據(jù)寫(xiě)入磁盤(pán)的策略是立即刷入磁盤(pán)(具體策略情況在下面小總結(jié)出會(huì)詳細(xì)介紹)
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-QBFN7sxV-1629818634042)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1625380795909.png)]
如果 redo log Buffer 刷入磁盤(pán)后,數(shù)據(jù)庫(kù)服務(wù)器宕機(jī)了,那我們更新的數(shù)據(jù)怎么辦?此時(shí)數(shù)據(jù)是在內(nèi)存中,數(shù)據(jù)豈不是丟失了?不,這次數(shù)據(jù)就不會(huì)丟失了,因?yàn)?redo log buffer 中的數(shù)據(jù)已經(jīng)被寫(xiě)入到磁盤(pán)了,已經(jīng)被持久化了,就算數(shù)據(jù)庫(kù)宕機(jī)了,在下次重啟的時(shí)候 MySQL 也會(huì)將 redo 日志文件內(nèi)容恢復(fù)到 Buffer Pool 中(這邊我的理解是和 Redis 的持久化機(jī)制是差不多的,在 Redis 啟動(dòng)的時(shí)候會(huì)檢查 rdb 或者是 aof 或者是兩者都檢查,根據(jù)持久化的文件來(lái)將數(shù)據(jù)恢復(fù)到內(nèi)存中)
到此為止,從【執(zhí)行器開(kāi)始調(diào)用存儲(chǔ)引擎接口】做了哪些事情呢?
1.準(zhǔn)備更新一條 SQL 語(yǔ)句 2.MySQL(innodb)會(huì)先去緩沖池(BufferPool)中去查找這條數(shù)據(jù),沒(méi)找到就會(huì)去磁盤(pán)中查找,如果查找到就會(huì)將這條數(shù)據(jù)加載到緩沖池(BufferPool)中 3.在加載到 Buffer Pool 的同時(shí),會(huì)將這條數(shù)據(jù)的原始記錄保存到 undo 日志文件中 4.innodb 會(huì)在 Buffer Pool 中執(zhí)行更新操作 5.更新后的數(shù)據(jù)會(huì)記錄在 redo log buffer 中 6.MySQL 提交事務(wù)的時(shí)候,會(huì)將 redo log buffer 中的數(shù)據(jù)寫(xiě)入到 redo 日志文件中 刷磁盤(pán)可以通過(guò) innodb_flush_log_at_trx_commit 參數(shù)來(lái)設(shè)置 值為 0 表示不刷入磁盤(pán) 值為 1 表示立即刷入磁盤(pán) 值為 2 表示先刷到 os cache 7.myslq 重啟的時(shí)候會(huì)將 redo 日志恢復(fù)到緩沖池中截止到目前位置,MySQL 的【執(zhí)行器】調(diào)用【存儲(chǔ)引擎的接口】去執(zhí)行【執(zhí)行計(jì)劃】提供的 SQL 的時(shí)候 InnoDB 做了哪些事情也就基本差不多了,但是這還沒(méi)完。下面還需要介紹下 MySQL 級(jí)別的日志文件 bin log
bin log 日志文件:記錄整個(gè)操作過(guò)程
上面介紹到的**redo log是 InnoDB 存儲(chǔ)引擎特有的日志文件,而bin log屬于是 MySQL 級(jí)別的日志**。redo log記錄的東西是偏向于物理性質(zhì)的,如:“對(duì)什么數(shù)據(jù),做了什么修改”。bin log是偏向于邏輯性質(zhì)的,類(lèi)似于:“對(duì) students 表中的 id 為 1 的記錄做了更新操作” 兩者的主要特點(diǎn)總結(jié)如下:
| 文件大小 | redo log 的大小是固定的(配置中也可以設(shè)置,一般默認(rèn)的就足夠了) | bin log 可通過(guò)配置參數(shù)max_bin log_size設(shè)置每個(gè)bin log文件的大小(但是一般不建議修改)。 |
| 實(shí)現(xiàn)方式 | redo log是InnoDB引擎層實(shí)現(xiàn)的(也就是說(shuō)是 Innodb 存儲(chǔ)引起過(guò)獨(dú)有的) | bin log是 MySQL 層實(shí)現(xiàn)的,所有引擎都可以使用 bin log日志 |
| 記錄方式 | redo log 采用循環(huán)寫(xiě)的方式記錄,當(dāng)寫(xiě)到結(jié)尾時(shí),會(huì)回到開(kāi)頭循環(huán)寫(xiě)日志。 | bin log 通過(guò)追加的方式記錄,當(dāng)文件大小大于給定值后,后續(xù)的日志會(huì)記錄到新的文件上 |
| 使用場(chǎng)景 | redo log適用于崩潰恢復(fù)(crash-safe)(這一點(diǎn)其實(shí)非常類(lèi)似與 Redis 的持久化特征) | bin log適用于主從復(fù)制和數(shù)據(jù)恢復(fù) |
bin log文件是如何刷入磁盤(pán)的?
bin log 的刷盤(pán)是有相關(guān)的策略的,策略可以通過(guò)sync_bin log來(lái)修改,默認(rèn)為 0,表示先寫(xiě)入 os cache,也就是說(shuō)在提交事務(wù)的時(shí)候,數(shù)據(jù)不會(huì)直接到磁盤(pán)中,這樣如果宕機(jī)bin log數(shù)據(jù)仍然會(huì)丟失。所以建議將sync_bin log設(shè)置為 1 表示直接將數(shù)據(jù)寫(xiě)入到磁盤(pán)文件中。
刷入 bin log 有以下幾種【模式】
1、 STATMENT
基于 SQL 語(yǔ)句的復(fù)制(statement-based replication, SBR),每一條會(huì)修改數(shù)據(jù)的 SQL 語(yǔ)句會(huì)記錄到 bin log 中
【優(yōu)點(diǎn)】:不需要記錄每一行的變化,減少了 bin log 日志量,節(jié)約了 IO , 從而提高了性能
【缺點(diǎn)】:在某些情況下會(huì)導(dǎo)致主從數(shù)據(jù)不一致,比如執(zhí)行sysdate()、slepp()等
2、ROW
基于行的復(fù)制(row-based replication, RBR),不記錄每條SQL語(yǔ)句的上下文信息,僅需記錄哪條數(shù)據(jù)被修改了
【優(yōu)點(diǎn)】:不會(huì)出現(xiàn)某些特定情況下的存儲(chǔ)過(guò)程、或 function、或 trigger 的調(diào)用和觸發(fā)無(wú)法被正確復(fù)制的問(wèn)題
【缺點(diǎn)】:會(huì)產(chǎn)生大量的日志,尤其是 alter table 的時(shí)候會(huì)讓日志暴漲
3、MIXED
基于 STATMENT 和 ROW 兩種模式的混合復(fù)制( mixed-based replication, MBR ),一般的復(fù)制使用 STATEMENT 模式保存 bin log ,對(duì)于 STATEMENT 模式無(wú)法復(fù)制的操作使用 ROW 模式保存 bin log
那既然bin log也是日志文件,那它是在什么記錄數(shù)據(jù)的呢?
其實(shí) MySQL 在提交事務(wù)的時(shí)候,不僅僅會(huì)將 redo log buffer 中的數(shù)據(jù)寫(xiě)入到redo log 文件中,同時(shí)也會(huì)將本次修改的數(shù)據(jù)記錄到 bin log文件中,同時(shí)會(huì)將本次修改的bin log文件名和修改的內(nèi)容在bin log中的位置記錄到redo log中,最后還會(huì)在redo log最后寫(xiě)入 commit 標(biāo)記,這樣就表示本次事務(wù)被成功的提交了。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-Qo6PV4lC-1629818634043)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1625383328114.png)]
如果在數(shù)據(jù)被寫(xiě)入到bin log文件的時(shí)候,剛寫(xiě)完,數(shù)據(jù)庫(kù)宕機(jī)了,數(shù)據(jù)會(huì)丟失嗎?
首先可以確定的是,只要redo log最后沒(méi)有 commit 標(biāo)記,說(shuō)明本次的事務(wù)一定是失敗的。但是數(shù)據(jù)是沒(méi)有丟失了,因?yàn)橐呀?jīng)被記錄到redo log的磁盤(pán)文件中了。在 MySQL 重啟的時(shí)候,就會(huì)將 redo log 中的數(shù)據(jù)恢復(fù)(加載)到Buffer Pool中。
好了,到目前為止,一個(gè)更新操作我們基本介紹得差不多,但是你有沒(méi)有感覺(jué)少了哪件事情還沒(méi)有做?是不是你也發(fā)現(xiàn)這個(gè)時(shí)候被更新記錄僅僅是在內(nèi)存中執(zhí)行的,哪怕是宕機(jī)又恢復(fù)了也僅僅是將更新后的記錄加載到Buffer Pool中,這個(gè)時(shí)候 MySQL 數(shù)據(jù)庫(kù)中的這條記錄依舊是舊值,也就是說(shuō)內(nèi)存中的數(shù)據(jù)在我們看來(lái)依舊是臟數(shù)據(jù),那這個(gè)時(shí)候怎么辦呢?
其實(shí) MySQL 會(huì)有一個(gè)【后臺(tái)線(xiàn)程】,它會(huì)在某個(gè)時(shí)機(jī)將我們Buffer Pool中的臟數(shù)據(jù)刷到 MySQL 數(shù)據(jù)庫(kù)中,這樣就將內(nèi)存和數(shù)據(jù)庫(kù)的數(shù)據(jù)保持統(tǒng)一了。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-xaea71qR-1629818634044)(C:\Users\17996\AppData\Roaming\Typora\typora-user-images\1625384652041.png)]
到此,關(guān)于Buffer Pool、Redo Log Buffer 和undo log、redo log、bin log 概念以及關(guān)系就基本差不多了。
我們?cè)倩仡櫹?/p> 1. Buffer Pool 是 MySQL 的一個(gè)非常重要的組件,因?yàn)獒槍?duì)數(shù)據(jù)庫(kù)的增刪改操作都是在 Buffer Pool 中完成的 2. Undo log 記錄的是數(shù)據(jù)操作前的樣子 3. redo log 記錄的是數(shù)據(jù)被操作后的樣子(redo log 是 Innodb 存儲(chǔ)引擎特有) 4. bin log 記錄的是整個(gè)操作記錄(這個(gè)對(duì)于主從復(fù)制具有非常重要的意義)
從準(zhǔn)備更新一條數(shù)據(jù)到事務(wù)的提交的流程描述
1. 首先執(zhí)行器根據(jù) MySQL 的執(zhí)行計(jì)劃來(lái)查詢(xún)數(shù)據(jù),先是從緩存池中查詢(xún)數(shù)據(jù),如果沒(méi)有就會(huì)去數(shù)據(jù)庫(kù)中查詢(xún),如果查詢(xún)到了就將其放到緩存池中 2. 在數(shù)據(jù)被緩存到緩存池的同時(shí),會(huì)寫(xiě)入 undo log 日志文件 3. 更新的動(dòng)作是在 BufferPool 中完成的,同時(shí)會(huì)將更新后的數(shù)據(jù)添加到 redo log buffer 中 4. 完成以后就可以提交事務(wù),在提交的同時(shí)會(huì)做以下三件事 5. (第一件事)將redo log buffer中的數(shù)據(jù)刷入到 redo log 文件中 6. (第二件事)將本次操作記錄寫(xiě)入到 bin log文件中 7. (第三件事)將 bin log 文件名字和更新內(nèi)容在 bin log 中的位置記錄到redo log中,同時(shí)在 redo log 最后添加 commit 標(biāo)記至此表示整個(gè)更新事務(wù)已經(jīng)完成
文章原文:鏈接
binlog
binlog,即二進(jìn)制日志,它記錄了數(shù)據(jù)庫(kù)上的所有改變,并以二進(jìn)制的形式保存在磁盤(pán)中;它可以用來(lái)查看數(shù)據(jù)庫(kù)的變更歷史、數(shù)據(jù)庫(kù)增量備份和恢復(fù)、MySQL的復(fù)制(主從數(shù)據(jù)庫(kù)的復(fù)制)。
binlog有三種格式
1、【Statement】基于SQL語(yǔ)句的復(fù)制(statement-based replication,SBR),每一條會(huì)修改數(shù)據(jù)的sql都會(huì)記錄在binlog中。
優(yōu)點(diǎn):不需要記錄每一行的變化,減少了binlog日志量,節(jié)約了IO,提高性能。 缺點(diǎn):由于記錄的只是執(zhí)行語(yǔ)句,為了這些語(yǔ)句能在slave上正確運(yùn)行,因此還必須記錄每條語(yǔ)句在執(zhí)行的時(shí)候的一些相關(guān)信息,以保證所有語(yǔ)句能在slave得到和在master端執(zhí)行時(shí)候相同 的結(jié)果。另外mysql 的復(fù)制,像一些特定函數(shù)功能,slave可與master上要保持一致會(huì)有很多相關(guān)問(wèn)題。 ps:相比row能節(jié)約多少性能與日志量,這個(gè)取決于應(yīng)用的SQL情況,正常同一條記錄修改或者插入row格式所產(chǎn)生的日志量還小于Statement產(chǎn)生的日志量,但是考慮到如果帶條件的update操作,以及整表刪除,alter表等操作,ROW格式會(huì)產(chǎn)生大量日志,因此在考慮是否使用ROW格式日志時(shí)應(yīng)該跟據(jù)應(yīng)用的實(shí)際情況,其所產(chǎn)生的日志量會(huì)增加多少,以及帶來(lái)的IO性能問(wèn)題。2、【Row】基于行的復(fù)制(row-based replication,RBR),5.1.5版本的MySQL才開(kāi)始支持row level的復(fù)制,它不記錄sql語(yǔ)句上下文相關(guān)信息,僅保存哪條記錄被修改。
優(yōu)點(diǎn): binlog中可以不記錄執(zhí)行的sql語(yǔ)句的上下文相關(guān)的信息,僅需要記錄那一條記錄被修改成什么了。所以rowlevel的日志內(nèi)容會(huì)非常清楚的記錄下每一行數(shù)據(jù)修改的細(xì)節(jié)。而且不會(huì)出現(xiàn)某些特定情況下的存儲(chǔ)過(guò)程,或function,以及trigger的調(diào)用和觸發(fā)無(wú)法被正確復(fù)制的問(wèn)題. 缺點(diǎn):所有的執(zhí)行的語(yǔ)句當(dāng)記錄到日志中的時(shí)候,都將以每行記錄的修改來(lái)記錄,這樣可能會(huì)產(chǎn)生大量的日志內(nèi)容。 ps:新版本的MySQL中對(duì)row level模式也被做了優(yōu)化,并不是所有的修改都會(huì)以row level來(lái)記錄,像遇到表結(jié)構(gòu)變更的時(shí)候就會(huì)以statement模式來(lái)記錄,如果sql語(yǔ)句確實(shí)就是update或者delete等修改數(shù)據(jù)的語(yǔ)句,那么還是會(huì)記錄所有行的變更。3、【Mixed】混合模式復(fù)制(mixed-based replication,MBR)從5.1.8版本開(kāi)始,MySQL提供了Mixed格式,實(shí)際上就是Statement與Row的結(jié)合。
在Mixed模式下,一般的語(yǔ)句修改使用statment格式保存binlog,如一些函數(shù),statement無(wú)法完成主從復(fù)制的操作,則采用row格式保存binlog,MySQL會(huì)根據(jù)執(zhí)行的每一條具體的sql語(yǔ)句來(lái)區(qū)分對(duì)待記錄的日志形式,也就是在Statement和Row之間選擇一種。查看是否開(kāi)啟了binlog
mysql> show variables like 'log_%'; +---------------------------------+-------------+ | Variable_name | Value | +---------------------------------+-------------+ | log_bin | OFF | | log_bin_trust_function_creators | OFF | | log_error | .\mysql.err | | log_queries_not_using_indexes | OFF | | log_slave_updates | OFF | | log_slow_queries | ON | | log_warnings | 1 | +---------------------------------+-------------+mysql的配置文件my.ini添加如下配置:
# Binary Logging. log-bin=mysql-bin binlog-format=Row重啟MySQL服務(wù)之后驗(yàn)證binlog是否開(kāi)啟:
show variables like 'log_bin'; show binary logs;binlog文件的位置:如果在修改my.ini的binlog時(shí)給的是全路徑,那么生成的日志文件就在指定的目錄下;如果如以上步驟中只給一個(gè)名字,那么生成的binlog日志的位置為(windwos為例):
C:\ProgramData\MySQL\MySQL Server 5.5\data服務(wù)重啟之后就會(huì)在指定目錄下產(chǎn)生mysql-bin.000001和mysql-bin.index文件。
查看指定binlog文件的內(nèi)容
mysql> show binlog events in 'mysql-bin.000001';只查看第一個(gè)binlog文件的內(nèi)容
mysql> show binlog events;查看帶有序號(hào)的binlog文件內(nèi)容
mysql> show binlog events in 'mysql-bin.000002'; +------------------+-----+-------------+-----------+-------------+----------------------+ | Log_name | Pos | Event_type | Server_id | End_log_pos | Info | +------------------+-----+-------------+-----------+-------------+----------------------+ | mysql-bin.000002 | 4 | Format_desc|1 |107 | Server ver: 5.5.58-log, Binlog ver: 4 | | mysql-bin.000002 |107| Query |1 | 175 | BEGIN | | mysql-bin.000002 |175| Query |1 |268 | use `test`; delete from bean where id = 10| | mysql-bin.000002 |268| Xid |1 |295 | COMMIT /* xid=4 */ | +------------------+-----+-------------+-----------+-------------+----------------------+ 4 rows in set (0.02 sec)1、當(dāng)停止或重啟服務(wù)器時(shí),服務(wù)器會(huì)把日志文件記入下一個(gè)日志文件,Mysql會(huì)在重啟時(shí)生成一個(gè)新的日志文件,文件序號(hào)遞增;
2、 如果日志文件超過(guò)max_binlog_size(默認(rèn)值1G)系統(tǒng)變量配置的上限時(shí),也會(huì)生成新的日志文件(在這里需要注意的是,如果你正使用大的事務(wù),二進(jìn)制日志還會(huì)超過(guò)max_binlog_size,不會(huì)生成新的日志文件,事務(wù)全寫(xiě)入一個(gè)二進(jìn)制日志中,這種情況主要是為了保證事務(wù)的完整性)
3、 日志被刷新時(shí),新生成一個(gè)日志文件。
相關(guān)參考
1、https://blog.jcole.us/innodb/
2、MySQL的數(shù)據(jù)存在磁盤(pán)上到底長(zhǎng)什么樣
3、阿里二面: 詳解一條 SQL 的執(zhí)行過(guò)程:https://mp.weixin.qq.com/s?__biz=MzU4ODI1MjA3NQ==&mid=2247499508&idx=2&sn=446d53f109c058b4c389cbfe7b0c3c31&chksm=fddd2830caaaa12650240644d91aa32bbc0754c993e0bd236a520c79e41370126d2d4e7e90fb&scene=132#wechat_redirect
PostreSQL
常用命令
linux命令行直接運(yùn)行pgsql命令
PGPASSWORD=password psql -h 127.0.0.1 -p 5432 -d basicframe -U basicframe -c "SELECT * FROM t_acc_user"shell腳本連接postgresql并操作數(shù)據(jù)庫(kù)
#!/bin/shsql="UPDATE t_acc_user SET status = 'normal'" PGPASSWORD=pgsqlpwd psql -h 127.0.0.1 -p 5432 -d basicframe -U basicframe -c "$sql"切換用戶(hù)
su postgres bash-4.2$ psql -d basicframe psql (9.3.6) Type "help" for help. basicframe=# select * from t_acc_master;分組后拼接多行
分組查詢(xún)結(jié)果做字段拼接
| 1 | 張三 |
| 2 | 李四 |
| 3 | 王五 |
| 1 | 張四 |
| 2 | 李五 |
| 2 | 李一 |
需求是根據(jù)name_type分組,李姓的在一組,張姓的分為一組,查詢(xún)結(jié)果如下:
| 1 | 張三,張四 |
| 2 | 李四,李五,李一 |
| 3 | 王五 |
MySQL可以很方便的利用group_concat函數(shù)來(lái)實(shí)現(xiàn),但是postgres9.0版本之前沒(méi)有這樣的函數(shù),需要進(jìn)行自定義函數(shù)【參考博客】。我們可以用**【array_agg()】,【string_agg()】**等函數(shù)來(lái)實(shí)現(xiàn)。注意string_agg()方法參數(shù)都必須為字符串。
array_agg
select name_type,array_to_string(array_agg(name),',') as names from t_acc_user group by name_type;string_agg
select name_type, string_agg(name,',') as names from t_acc_user group by name_type;根據(jù)條件修改對(duì)應(yīng)字段
-- t_acc_master_mapping:映射表 name_old:舊 name_new:新 UPDATE t_acc_master t01 SET name = t02.name_new FROM (SELECT * FROM t_acc_master_mapping) t02 WHERE t01.name = t02.name_old AND t01.status = 'normal';鎖表問(wèn)題解決
在使用pgsql刪除數(shù)據(jù)庫(kù)表(DROP)操作時(shí)候出現(xiàn)阻塞的現(xiàn)象,由此懷疑是鎖表導(dǎo)致。
排查數(shù)據(jù)庫(kù)表是否鎖住
SELECT oid FROM pg_class WHERE relname = 't_user'; -- 可能鎖表的表名 SELECT pid FROM pg_locks WHERE relation = '2523'; -- 由上面查出的oid如果上面的SQL查詢(xún)到了結(jié)果,則表示該表被鎖,執(zhí)行下面SQL釋放鎖定
SELECT pg_cancel_backend('100045'); -- 上面查到的pidctid的淺談
ctid: 表示數(shù)據(jù)記錄的物理行當(dāng)信息,指的是 一條記錄位于哪個(gè)數(shù)據(jù)塊的哪個(gè)位移上面。 跟oracle中偽列 rowid 的意義一樣的,只是形式不一樣。例如這有個(gè)一表t_org_user,查看每行記錄的ctid情況:
SELECT ctid, * FROM t_org_user;查詢(xún)結(jié)果,格式(blockid,itemid),拿其中(0,1)來(lái)說(shuō),0表示塊id,1表示在這塊第一條記錄。
ctid | id | name | cnname (0,1) 1 data01 測(cè)試01 (0,2) 2 data02 測(cè)試02 (0,3) 3 data01 測(cè)試01 (0,4) 1 data01 測(cè)試01ctid去重
我們知道rowid在oracle有個(gè)重要的作用;被用作表記錄去重;同理 ctid在postgresql里面同樣可以使用。例如t_org_user表id為1有兩條記錄
DELETE FROM t_org_user WHERE ctid NOT IN ( SELECT min( ctid ) FROM t_org_user GROUP BY id );根據(jù)id去重后
ctid | id | name | cnname (0,1) 1 data01 測(cè)試01 (0,2) 2 data02 測(cè)試02 (0,3) 3 data01 測(cè)試01剛剛我們刪除了(0,4)這條記錄,現(xiàn)在我們重新插入一條新的記錄之后查詢(xún)
ctid | id | name | cnname (0,1) 1 data01 測(cè)試01 (0,2) 2 data02 測(cè)試02 (0,3) 3 data01 測(cè)試01 (0,5) 1 data01 測(cè)試01為什么不是(0,4),而是(0,5)?這個(gè)跟postgresql多版本事務(wù)有關(guān),這是postgresql的特性,postgresql里面沒(méi)有回滾段的概念,那怎么把(0,5)在顯示呢?想這塊(0,5)的空間再存放數(shù)據(jù),postgresql里面有AUTOVACUUM進(jìn)程,當(dāng)然我們也可以手動(dòng)回收這段空間;
刪除(0,5)這條數(shù)據(jù)之后執(zhí)行:
vacuum t_org_user;再次插入之后就是從(0,4)開(kāi)始的,vacuum: 回收未顯示的物理位置;標(biāo)明可以繼續(xù)使用。
select relpages,reltuples from pg_class where relname = 't_org_user';我們可以借助系統(tǒng)視圖pg_class,其中relpages,reltuples分別代表塊數(shù),記錄數(shù)
generate_series序列函數(shù)
INSERT INTO t_org_user SELECT generate_series ( 1, 1000 ), 'data' || generate_series ( 1, 1000 ), '中文' || generate_series ( 1, 1000 );generate_series為一個(gè)序列函數(shù),例如產(chǎn)生1-100就是generate_series(1,100),產(chǎn)生0-100直接的偶數(shù)就是generate_series(0,100,2),其中的0表示序列開(kāi)始位置;100代表結(jié)束位置;2為偏移量。
未指定字段類(lèi)型問(wèn)題(type “unknown”)
SELECT (array_to_string(array_agg(t.cmdaudit),'#!')) as cmdaudit FROM ( SELECT 'test-new-xx' as cmdaudit from t_acc_master ) tpostgres數(shù)據(jù)庫(kù)較低版本(9.3.6)存在以下問(wèn)題,這里的問(wèn)題是'' as name實(shí)際上并沒(méi)有為值指定類(lèi)型。這是unknown類(lèi)型,而 PostgreSQL 通常從諸如您將其插入到哪個(gè)列或您將其傳遞給哪個(gè)函數(shù)之類(lèi)的東西推斷出真正的類(lèi)型。
Could not determine polymorphic type because input has type "unknown"類(lèi)型化定義
TEXT '' AS nameCAST
CAST('' AS text) AS namePostgreSQL 簡(jiǎn)寫(xiě)
''::text案例
SELECT (array_to_string(array_agg(t.cmdaudit),'#!')) as cmdaudit FROM ( SELECT 'test-new-xx' ::text as cmdaudit from t_acc_master ) tSELECT (array_to_string(array_agg(t.cmdaudit),'#!')) as cmdaudit FROM ( SELECT VARCHAR 'test-new-xx' as cmdaudit from t_acc_master ) tSELECT (array_to_string(array_agg(t.cmdaudit),'#!')) as cmdaudit FROM ( SELECT TEXT 'test-new-xx' as cmdaudit from t_acc_master ) tSELECT (array_to_string(array_agg(t.cmdaudit),'#!')) as cmdaudit FROM ( SELECT CAST('test-new-xx' AS text) as cmdaudit from t_acc_master ) t自定義函數(shù)索引丟失
測(cè)試場(chǎng)景: t_auth_r_master_slave授權(quán)表 200w 行數(shù)據(jù),masterId、resId有索引 :
CREATE INDEX resid_index ON t_auth_r_master_slave (resid); CREATE INDEX masterid_index ON t_auth_r_master_slave (masterid);EXPLAIN ANALYZE進(jìn)行分析
EXPLAIN ANALYZE SELECT * FROM t_auth_r_master_slave LIMIT 10 SELECT count(*) FROM t_auth_r_master_slave使用函數(shù)get_master_auth耗時(shí)11s
EXPLAIN ANALYZEselectt.id,t.ruleid,t.masterid,t.resid from get_master_auth('del') t where t.masterid = '1140131' and t.resid = '38100217' and t.slaveid is null;Function Scan on get_master_auth t (cost=0.25..15.25 rows=1 width=872) (actual time=9719.881..10632.622 rows=2 loops=1)Filter: (((masterid)::text = '1140131'::text) AND ((resid)::text = '38100217'::text))Rows Removed by Filter: 2463409 Total runtime: 10721.853 ms使用原始sql耗時(shí)0.01s
EXPLAIN ANALYZE selectt.id,t.ruleid,t.masterid,t.resid from (SELECT t.* FROM t_auth_r_master_slave tJOIN t_acc_master m ON m.id=t.masterid AND m.status !='del'JOIN t_auth_res r ON r.id=t.resid AND r.status !='del'JOIN t_acc_slave s ON s.id=t.slaveid and s.status != 'del'WHERE t.status!='del'UNION ALLSELECT t.* FROM t_auth_r_master_slave tJOIN t_acc_master m ON m.id=t.masterid AND m.status !='del'JOIN t_auth_res r ON r.id=t.resid AND r.status !='del'WHERE t.status!='del' and t.slaveid is null) t where t.masterid = '1' and t.resid = '1' and t.slaveid is null;匹配上索引:Bitmap Index Scan on resid_index…
Append (cost=749.97..1555.83 rows=2 width=35) (actual time=513.408..513.408 rows=0 loops=1)-> Subquery Scan on "*SELECT* 1" (cost=749.97..782.07 rows=1 width=35) (actual time=506.057..506.057 rows=0 loops=1)-> Nested Loop (cost=749.97..782.06 rows=1 width=1398) (actual time=506.055..506.055 rows=0 loops=1)-> Nested Loop (cost=749.68..773.74 rows=1 width=1398) (actual time=506.053..506.053 rows=0 loops=1)-> Nested Loop (cost=749.39..765.43 rows=1 width=1398) (actual time=506.052..506.052 rows=0 loops=1)-> Bitmap Heap Scan on t_auth_r_master_slave t (cost=749.11..757.12 rows=1 width=1398) (actual time=506.050..506.050 rows=0 loops=1)Recheck Cond: (((resid)::text = '38100217'::text) AND ((masterid)::text = '1140131'::text))Rows Removed by Index Recheck: 9Filter: ((slaveid IS NULL) AND ((status)::text <> 'del'::text))Rows Removed by Filter: 2-> BitmapAnd (cost=749.11..749.11 rows=2 width=0) (actual time=505.392..505.392 rows=0 loops=1)-> Bitmap Index Scan on resid_index (cost=0.00..14.54 rows=281 width=0) (actual time=12.929..12.929 rows=70 loops=1)Index Cond: ((resid)::text = '38100217'::text)-> Bitmap Index Scan on masterid_index (cost=0.00..734.32 rows=21586 width=0) (actual time=492.214..492.214 rows=22169 loops=1)Index Cond: ((masterid)::text = '1140131'::text)-> Index Scan using t_acc_master_pkey on t_acc_master m (cost=0.28..8.30 rows=1 width=7) (never executed)Index Cond: ((id)::text = '1140131'::text)Filter: ((status)::text <> 'del'::text)-> Index Scan using t_auth_res_pkey on t_auth_res r (cost=0.29..8.31 rows=1 width=9) (never executed)Index Cond: ((id)::text = '38100217'::text)Filter: ((status)::text <> 'del'::text)-> Index Scan using t_acc_slave_pkey on t_acc_slave s (cost=0.29..8.31 rows=1 width=9) (never executed)Index Cond: ((id)::text = (t.slaveid)::text)Filter: ((status)::text <> 'del'::text)-> Subquery Scan on "*SELECT* 2" (cost=749.68..773.75 rows=1 width=35) (actual time=7.348..7.348 rows=0 loops=1)-> Nested Loop (cost=749.68..773.74 rows=1 width=1398) (actual time=7.346..7.346 rows=0 loops=1)-> Nested Loop (cost=749.39..765.43 rows=1 width=1398) (actual time=7.345..7.345 rows=0 loops=1)-> Bitmap Heap Scan on t_auth_r_master_slave t_1 (cost=749.11..757.12 rows=1 width=1398) (actual time=7.343..7.343 rows=0 loops=1)Recheck Cond: (((resid)::text = '38100217'::text) AND ((masterid)::text = '1140131'::text))Rows Removed by Index Recheck: 9Filter: ((slaveid IS NULL) AND (slaveid IS NULL) AND ((status)::text <> 'del'::text))Rows Removed by Filter: 2-> BitmapAnd (cost=749.11..749.11 rows=2 width=0) (actual time=7.315..7.315 rows=0 loops=1)-> Bitmap Index Scan on resid_index (cost=0.00..14.54 rows=281 width=0) (actual time=0.075..0.075 rows=70 loops=1)Index Cond: ((resid)::text = '38100217'::text)-> Bitmap Index Scan on masterid_index (cost=0.00..734.32 rows=21586 width=0) (actual time=7.122..7.122 rows=22169 loops=1)Index Cond: ((masterid)::text = '1140131'::text)-> Index Scan using t_acc_master_pkey on t_acc_master m_1 (cost=0.28..8.30 rows=1 width=7) (never executed)Index Cond: ((id)::text = '1140131'::text)Filter: ((status)::text <> 'del'::text)-> Index Scan using t_auth_res_pkey on t_auth_res r_1 (cost=0.29..8.31 rows=1 width=9) (never executed)Index Cond: ((id)::text = '38100217'::text)Filter: ((status)::text <> 'del'::text) Total runtime: 514.501 mshttps://www.jb51.cc/postgresql/192456.html
https://blog.csdn.net/u011944141/article/details/98056440
相關(guān)參考
pgsql:https://www.cnblogs.com/lottu/category/535826.html
pgsql數(shù)據(jù)遷移:https://www.cnblogs.com/lottu/category/838299.html
pgsql高可用:https://www.cnblogs.com/lottu/category/841292.html
Oracle
總結(jié)
- 上一篇: javax.servlet-api 简介
- 下一篇: Mongo数据库的操作