日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

          歡迎訪問(wèn) 生活随笔!

          生活随笔

          當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

          数据库

          《MySQL实战45讲》基础理论篇 1-8讲 学习笔记

          發(fā)布時(shí)間:2024/4/11 数据库 33 豆豆
          生活随笔 收集整理的這篇文章主要介紹了 《MySQL实战45讲》基础理论篇 1-8讲 学习笔记 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

          圖片來(lái)自于極客時(shí)間,如有版權(quán)問(wèn)題,請(qǐng)聯(lián)系我刪除。

          對(duì)于索引一些比較了解的,記錄的比較少,感興趣的可以看我其他MySQL的文章

          01 | 基礎(chǔ)架構(gòu):一條SQL查詢語(yǔ)句是如何執(zhí)行的?

          1.連接器

          如果用戶名密碼認(rèn)證通過(guò),連接器會(huì)到權(quán)限表里面查出你擁有的權(quán)限。之后,這個(gè)連接里面的權(quán)限判斷邏輯,都將依賴于此時(shí)讀到的權(quán)限。

          這就意味著,一個(gè)用戶成功建立連接后,即使你用管理員賬號(hào)對(duì)這個(gè)用戶的權(quán)限做了修改,也不會(huì)影響已經(jīng)存在連接的權(quán)限。修改完成后,只有再新建的連接才會(huì)使用新的權(quán)限設(shè)置

          建立連接的過(guò)程通常是比較復(fù)雜的,所以在使用中要盡量減少建立連接的動(dòng)作,也就是盡量使用長(zhǎng)連接。

          但是全部使用長(zhǎng)連接后,有些時(shí)候 MySQL 占用內(nèi)存漲得特別快,這是因?yàn)?MySQL 在執(zhí)行過(guò)程中臨時(shí)使用的內(nèi)存是管理在連接對(duì)象里面的。這些資源會(huì)在連接斷開的時(shí)候才釋放。所以如果長(zhǎng)連接累積下來(lái),可能導(dǎo)致內(nèi)存占用太大,被系統(tǒng)強(qiáng)行殺掉(OOM),從現(xiàn)象看就是 MySQL 異常重啟了。

          怎么解決這個(gè)問(wèn)題呢?你可以考慮以下兩種方案。
          1.定期斷開長(zhǎng)連接。使用一段時(shí)間,或者程序里面判斷執(zhí)行過(guò)一個(gè)占用內(nèi)存的大查詢后,斷開連接,之后要查詢?cè)僦剡B。
          2.如果你用的是 MySQL 5.7 或更新版本,可以在每次執(zhí)行一個(gè)比較大的操作后,通過(guò)執(zhí)行 mysql_reset_connection 來(lái)重新初始化連接資源。這個(gè)過(guò)程不需要重連和重新做權(quán)限驗(yàn)證,但是會(huì)將連接恢復(fù)到剛剛創(chuàng)建完時(shí)的狀態(tài)

          2.查詢緩存

          查詢緩存的失效非常頻繁,不建議使用
          MySQL 提供“按需使用”的方式。你可以將參數(shù) query_cache_type 設(shè)置成 DEMAND,這樣對(duì)于默認(rèn)的 SQL 語(yǔ)句都不使用查詢緩存。而對(duì)于你確定要使用查詢緩存的語(yǔ)句,可以用 SQL_CACHE 顯式指定,像下面這個(gè)語(yǔ)句一樣

          mysql> select SQL_CACHE * from T where ID=10;

          需要注意的是,MySQL 8.0 版本直接將查詢緩存的整塊功能刪掉了,也就是說(shuō) 8.0 開始徹底沒(méi)有這個(gè)功能了

          3.分析器

          詞法分析 語(yǔ)法分析(是否滿足MySQL語(yǔ)法)

          4.優(yōu)化器

          優(yōu)化器是在表里面有多個(gè)索引的時(shí)候,決定使用哪個(gè)索引;或者在一個(gè)語(yǔ)句有多表關(guān)聯(lián)(join)的時(shí)候,決定各個(gè)表的連接順序
          優(yōu)化器階段完成后,這個(gè)語(yǔ)句的執(zhí)行方案就確定下來(lái)了

          5.執(zhí)行器

          開始執(zhí)行的時(shí)候,要先判斷一下你對(duì)這個(gè)表 T 有沒(méi)有執(zhí)行查詢的權(quán)限,如果沒(méi)有,就會(huì)返回沒(méi)有權(quán)限的錯(cuò)誤, (在工程實(shí)現(xiàn)上,如果命中查詢緩存,會(huì)在查詢緩存返回結(jié)果的時(shí)候做權(quán)限驗(yàn)證。查詢也會(huì)在優(yōu)化器之前調(diào)用 precheck 驗(yàn)證權(quán)限)

          mysql> select * from T where ID=10;
          ERROR 1142 (42000): SELECT command denied to user ‘b’@‘localhost’ for table ‘T’

          如果有權(quán)限,就打開表繼續(xù)執(zhí)行。打開表的時(shí)候,執(zhí)行器就會(huì)根據(jù)表的引擎定義,去使用這個(gè)引擎提供的接口。
          比如我們這個(gè)例子中的表 T 中,ID 字段沒(méi)有索引,那么執(zhí)行器的執(zhí)行流程是這樣的:

        1. 調(diào)用 InnoDB 引擎接口取這個(gè)表的第一行,判斷 ID 值是不是 10,如果不是則跳過(guò),如果是則將這行存在結(jié)果集中;
        2. 調(diào)用引擎接口取“下一行”,重復(fù)相同的判斷邏輯,直到取到這個(gè)表的最后一行。
        3. 執(zhí)行器將上述遍歷過(guò)程中所有滿足條件的行組成的記錄集作為結(jié)果集返回給客戶端。
        4. 你會(huì)在數(shù)據(jù)庫(kù)的慢查詢?nèi)罩局锌吹揭粋€(gè) rows_examined 的字段,表示這個(gè)語(yǔ)句執(zhí)行過(guò)程中掃描了多少行。
          這個(gè)值就是在執(zhí)行器每次調(diào)用引擎獲取數(shù)據(jù)行的時(shí)候累加的。在有些場(chǎng)景下,執(zhí)行器調(diào)用一次,在引擎內(nèi)部則掃描了多行,因此引擎掃描行數(shù)跟 rows_examined 并不是完全相同的

          02 | 日志系統(tǒng):一條SQL更新語(yǔ)句是如何執(zhí)行的?

          redo log

          為了提高更新效率,MySQL使用了 WAL 技術(shù),WAL 的全稱是 Write-Ahead Logging,它的關(guān)鍵點(diǎn)就是先寫日志,再寫磁盤

          具體來(lái)說(shuō),當(dāng)有一條記錄需要更新的時(shí)候,InnoDB 引擎就會(huì)先把記錄寫到 redo log(粉板)里面,并更新內(nèi)存,這個(gè)時(shí)候更新就算完成了。同時(shí),InnoDB 引擎會(huì)在適當(dāng)?shù)臅r(shí)候,將這個(gè)操作記錄更新到磁盤里面,而這個(gè)更新往往是在系統(tǒng)比較空閑的時(shí)候做。

          InnoDB 的 redo log 是固定大小的,比如可以配置為一組 4 個(gè)文件,每個(gè)文件的大小是 1GB,那么這塊“粉板”總共就可以記錄 4GB 的操作。從頭開始寫,寫到末尾就又回到開頭循環(huán)寫,如下面這個(gè)圖所示。

          write pos 是當(dāng)前記錄的位置,一邊寫一邊后移,寫到第 3 號(hào)文件末尾后就回到 0 號(hào)文件開頭。checkpoint 是當(dāng)前要擦除的位置,也是往后推移并且循環(huán)的,擦除記錄前要把記錄更新到數(shù)據(jù)文件。

          write pos 和 checkpoint 之間的是“粉板”上還空著的部分,可以用來(lái)記錄新的操作。如果 write pos 追上 checkpoint,表示“粉板”滿了,這時(shí)候不能再執(zhí)行新的更新,得停下來(lái)先擦掉一些記錄,把 checkpoint 推進(jìn)一下。

          有了 redo log,InnoDB 就可以保證即使數(shù)據(jù)庫(kù)發(fā)生異常重啟,之前提交的記錄都不會(huì)丟失,這個(gè)能力稱為 crash-safe。

          binlog

          上面我們聊到的redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己的日志,稱為 binlog(歸檔日志)。

          為什么會(huì)有兩份日志呢?因?yàn)樽铋_始 MySQL 里并沒(méi)有 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,但是 MyISAM 沒(méi)有 crash-safe 的能力,binlog 日志只能用于歸檔。而 InnoDB 是另一個(gè)公司以插件形式引入 MySQL 的,既然只依靠 binlog 是沒(méi)有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系統(tǒng)——也就是 redo log 來(lái)實(shí)現(xiàn) crash-safe 能力。

          這兩種日志有以下三點(diǎn)不同。

        5. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實(shí)現(xiàn)的,所有引擎都可以使用。
        6. redo log 是物理日志,記錄的是“在某個(gè)數(shù)據(jù)頁(yè)上做了什么修改”;binlog 是邏輯日志,記錄的是這個(gè)語(yǔ)句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。
        7. Redo log不是記錄數(shù)據(jù)頁(yè)“更新之后的狀態(tài)”,而是記錄這個(gè)頁(yè) “做了什么改動(dòng)”
          Binlog有兩種模式,statement 格式的話是記sql語(yǔ)句, row格式會(huì)記錄行的內(nèi)容,記兩條,更新前和更新后都有。

        8. redo log 是循環(huán)寫的,空間固定會(huì)用完(所以不具備歸檔功能);binlog 是可以追加寫入的。“追加寫”是指 binlog 文件寫到一定大小后會(huì)切換到下一個(gè),并不會(huì)覆蓋以前的日志
        9. 兩階段提交

          怎樣讓數(shù)據(jù)庫(kù)恢復(fù)到半個(gè)月內(nèi)任意一秒的狀態(tài)?

          binlog 會(huì)記錄所有的邏輯操作,采用“追加寫”的形式。如果你的 DBA 承諾說(shuō)半個(gè)月內(nèi)可以恢復(fù),那么備份系統(tǒng)中一定會(huì)保存最近半個(gè)月的所有 binlog,同時(shí)系統(tǒng)會(huì)定期做整庫(kù)備份(一天一備/一周一備)

          當(dāng)需要恢復(fù)到指定的某一秒時(shí),比如某天下午兩點(diǎn)發(fā)現(xiàn)中午十二點(diǎn)有一次誤刪表,需要找回?cái)?shù)據(jù),那你可以這么做:
          首先,找到最近的一次全量備份,如果你運(yùn)氣好,可能就是昨天晚上的一個(gè)備份,從這個(gè)備份恢復(fù)到臨時(shí)庫(kù);
          然后,從備份的時(shí)間點(diǎn)開始,將備份的 binlog 依次取出來(lái),重放到中午誤刪表之前的那個(gè)時(shí)刻。

          這樣你的臨時(shí)庫(kù)就跟誤刪之前的線上庫(kù)一樣了,然后你可以把表數(shù)據(jù)從臨時(shí)庫(kù)取出來(lái),按需要恢復(fù)到線上庫(kù)去

          redo log 用于保證 crash-safe 能力。innodb_flush_log_at_trx_commit 這個(gè)參數(shù)設(shè)置成 1 的時(shí)候,表示每次事務(wù)的 redo log 都直接持久化到磁盤。

          這個(gè)參數(shù)我建議你設(shè)置成 1,這樣可以保證 MySQL 異常重啟之后數(shù)據(jù)不丟失。sync_binlog 這個(gè)參數(shù)設(shè)置成 1 的時(shí)候,表示每次事務(wù)的 binlog 都持久化到磁盤。這個(gè)參數(shù)我也建議你設(shè)置成 1,這樣可以保證 MySQL 異常重啟之后 binlog 不丟失。

          MySQL 日志系統(tǒng)密切相關(guān)的“兩階段提交”。兩階段提交是跨系統(tǒng)維持?jǐn)?shù)據(jù)邏輯一致性時(shí)常用的一個(gè)方案,即使你不做數(shù)據(jù)庫(kù)內(nèi)核開發(fā),日常開發(fā)中也有可能會(huì)用到。

          問(wèn)題:為什么binlog不能用于 crash-safe?

          歷史上的原因:
          這個(gè)是一開始就這么設(shè)計(jì)的,所以不能只依賴binlog。

          操作上的原因是:binlog是可以關(guān)的,你如果有權(quán)限,可以set sql_log_bin=0關(guān)掉本線程的binlog日志。 所以只依賴binlog來(lái)恢復(fù)就靠不住。

          03 | 事務(wù)隔離:為什么你改了我還看不見?

          數(shù)據(jù)庫(kù)InnoDB不同隔離級(jí)別:

          在實(shí)現(xiàn)上,數(shù)據(jù)庫(kù)里面會(huì)創(chuàng)建一個(gè)視圖,訪問(wèn)的時(shí)候以視圖的邏輯結(jié)果為準(zhǔn)。
          在“可重復(fù)讀”隔離級(jí)別下,這個(gè)視圖是在事務(wù)啟動(dòng)時(shí)創(chuàng)建的,整個(gè)事務(wù)存在期間都用這個(gè)視圖。
          在“讀提交”隔離級(jí)別下,這個(gè)視圖是在每個(gè) SQL 語(yǔ)句開始執(zhí)行的時(shí)候創(chuàng)建的。這里需要注意的是,
          “讀未提交”隔離級(jí)別下直接返回記錄上的最新值,沒(méi)有視圖概念;
          而“串行化”隔離級(jí)別下直接用加鎖的方式來(lái)避免并行訪問(wèn)。

          你可能會(huì)問(wèn)那什么時(shí)候需要“可重復(fù)讀”的場(chǎng)景呢?
          我們來(lái)看一個(gè)數(shù)據(jù)校對(duì)邏輯的案例。假設(shè)你在管理一個(gè)個(gè)人銀行賬戶表。一個(gè)表存了賬戶余額,一個(gè)表存了賬單明細(xì)。到了月底你要做數(shù)據(jù)校對(duì),也就是判斷上個(gè)月的余額和當(dāng)前余額的差額,是否與本月的賬單明細(xì)一致。
          你一定希望在校對(duì)過(guò)程中,即使有用戶發(fā)生了一筆新的交易,也不影響你的校對(duì)結(jié)果。這時(shí)候使用“可重復(fù)讀”隔離級(jí)別就很方便。事務(wù)啟動(dòng)時(shí)的視圖可以認(rèn)為是靜態(tài)的,不受其他事務(wù)更新的影響。

          我們可以看到在不同的隔離級(jí)別下,數(shù)據(jù)庫(kù)行為是有所不同的。Oracle 數(shù)據(jù)庫(kù)的默認(rèn)隔離級(jí)別其實(shí)就是“讀提交”,因此對(duì)于一些從 Oracle 遷移到 MySQL 的應(yīng)用,為保證數(shù)據(jù)庫(kù)隔離級(jí)別的一致,你一定要記得將 MySQL 的隔離級(jí)別設(shè)置為“讀提交”。(transaction_isolation)

          在 MySQL 中,實(shí)際上每條記錄在更新的時(shí)候都會(huì)同時(shí)記錄一條回滾操作。記錄上的最新值,通過(guò)回滾操作,都可以得到前一個(gè)狀態(tài)的值。

          假設(shè)一個(gè)值從 1 被按順序改成了 2、3、4,在回滾日志里面就會(huì)有類似下面的記錄。

          回滾日志什么時(shí)候刪除呢?答案是,在不需要的時(shí)候才刪除。也就是說(shuō),系統(tǒng)會(huì)判斷,當(dāng)沒(méi)有事務(wù)再需要用到這些回滾日志時(shí),回滾日志會(huì)被刪除。

          為什么建議你盡量不要使用長(zhǎng)事務(wù)。
          長(zhǎng)事務(wù)意味著系統(tǒng)里面會(huì)存在很老的事務(wù)視圖。由于這些事務(wù)隨時(shí)可能訪問(wèn)數(shù)據(jù)庫(kù)里面的任何數(shù)據(jù),所以這個(gè)事務(wù)提交之前,數(shù)據(jù)庫(kù)里面它可能用到的回滾記錄都必須保留,這就會(huì)導(dǎo)致大量占用存儲(chǔ)空間。

          在 MySQL 5.5 及以前的版本,回滾日志是跟數(shù)據(jù)字典一起放在 ibdata 文件里的,即使長(zhǎng)事務(wù)最終提交,回滾段被清理,文件也不會(huì)變小。我見過(guò)數(shù)據(jù)只有 20GB,而回滾段有 200GB 的庫(kù)。最終只好為了清理回滾段,重建整個(gè)庫(kù)。除了對(duì)回滾段的影響,長(zhǎng)事務(wù)還占用鎖資源,也可能拖垮整個(gè)庫(kù)
          read-viewA事務(wù)未提交的情形下,不能將后面的回滾段刪除,哪怕事務(wù)B和C已經(jīng)提交

          有些客戶端連接框架會(huì)默認(rèn)連接成功后先執(zhí)行一個(gè) set autocommit=0 的命令。這就導(dǎo)致接下來(lái)的查詢都在事務(wù)中,如果是長(zhǎng)連接,就導(dǎo)致了意外的長(zhǎng)事務(wù)。
          因此,我會(huì)建議你總是使用 set autocommit=1, 通過(guò)顯式語(yǔ)句的方式來(lái)啟動(dòng)事務(wù)。

          但是有的開發(fā)同學(xué)會(huì)糾結(jié)“多一次交互”的問(wèn)題。對(duì)于一個(gè)需要頻繁使用事務(wù)的業(yè)務(wù)。如果你也有這個(gè)顧慮,建議你使用 commit work and chain 語(yǔ)法。在 autocommit 為 1 的情況下,用 begin 顯式啟動(dòng)的事務(wù),如果執(zhí)行 commit 則提交事務(wù)。如果執(zhí)行 commit work and chain,則是提交事務(wù)并自動(dòng)啟動(dòng)下一個(gè)事務(wù),這樣也省去了再次執(zhí)行 begin 語(yǔ)句的開銷。同時(shí)帶來(lái)的好處是從程序開發(fā)的角度明確地知道每個(gè)語(yǔ)句是否處于事務(wù)中。

          問(wèn)題解答:
          一天一備跟一周一備的對(duì)比:
          好處是“最長(zhǎng)恢復(fù)時(shí)間”更短。在一天一備的模式里,最壞情況下需要應(yīng)用一天的 binlog。比如,你每天 0 點(diǎn)做一次全量備份,而要恢復(fù)出一個(gè)到昨天晚上 23 點(diǎn)的備份。一周一備最壞情況就要應(yīng)用一周的 binlog 了。
          系統(tǒng)的對(duì)應(yīng)指標(biāo)就是 @尼古拉斯·趙四 @慕塔 提到的 RTO(恢復(fù)目標(biāo)時(shí)間)。當(dāng)然這個(gè)是有成本的,因?yàn)楦l繁全量備份需要消耗更多存儲(chǔ)空間,所以這個(gè) RTO 是成本換來(lái)的,就需要你根據(jù)業(yè)務(wù)重要性來(lái)評(píng)估了

          04 | 深入淺出索引(上)05 | 深入淺出索引(下)

          哈希表這種結(jié)構(gòu)適用于只有等值查詢的場(chǎng)景,比如 Memcached 及其他一些 NoSQL 引擎。

          有序數(shù)組在等值查詢和范圍查詢場(chǎng)景中的性能就都非常優(yōu)秀
          但是有序數(shù)組索引只適用于靜態(tài)存儲(chǔ)引擎,比如你要保存的是 2017 年某個(gè)城市的所有人口信息,這類不會(huì)再修改的數(shù)據(jù)。

          二叉樹是搜索效率最高的,但是實(shí)際上大多數(shù)的數(shù)據(jù)庫(kù)存儲(chǔ)卻并不使用二叉樹。其原因是,索引不止存在內(nèi)存中,還要寫到磁盤上。你可以想象一下一棵 100 萬(wàn)節(jié)點(diǎn)的平衡二叉樹,樹高 20。一次查詢可能需要訪問(wèn) 20 個(gè)數(shù)據(jù)塊

          可能在一些建表規(guī)范里面見到過(guò)類似的描述,要求建表語(yǔ)句里一定要有自增主鍵。當(dāng)然事無(wú)絕對(duì),我們來(lái)分析一下哪些場(chǎng)景下應(yīng)該使用自增主鍵,而哪些場(chǎng)景下不應(yīng)該。

          自增主鍵是指自增列上定義的主鍵,在建表語(yǔ)句中一般是這么定義的: NOT NULL PRIMARY KEY AUTO_INCREMENT。

          插入新記錄的時(shí)候可以不指定 ID 的值,系統(tǒng)會(huì)獲取當(dāng)前 ID 最大值加 1 作為下一條記錄的 ID 值。也就是說(shuō),自增主鍵的插入數(shù)據(jù)模式,正符合了我們前面提到的遞增插入的場(chǎng)景。每次插入一條新記錄,都是追加操作,都不涉及到挪動(dòng)其他記錄,也不會(huì)觸發(fā)葉子節(jié)點(diǎn)的分裂。
          而有業(yè)務(wù)邏輯的字段做主鍵,則往往不容易保證有序插入,這樣寫數(shù)據(jù)成本相對(duì)較高。除了考慮性能外,我們還可以從存儲(chǔ)空間的角度來(lái)看。
          假設(shè)你的表中確實(shí)有一個(gè)唯一字段,比如字符串類型的身份證號(hào),那應(yīng)該用身份證號(hào)做主鍵,還是用自增字段做主鍵呢?由于每個(gè)非主鍵索引的葉子節(jié)點(diǎn)上都是主鍵的值。如果用身份證號(hào)做主鍵,那么每個(gè)二級(jí)索引的葉子節(jié)點(diǎn)占用約 20 個(gè)字節(jié),而如果用整型做主鍵,則只要 4 個(gè)字節(jié),如果是長(zhǎng)整型(bigint)則是 8 個(gè)字節(jié)。

          顯然,主鍵長(zhǎng)度越小,普通索引的葉子節(jié)點(diǎn)就越小,普通索引占用的空間也就越小。

          所以,從性能和存儲(chǔ)空間方面考量,自增主鍵往往是更合理的選擇

          有沒(méi)有什么場(chǎng)景適合用業(yè)務(wù)字段直接做主鍵的呢?
          比如,有些業(yè)務(wù)的場(chǎng)景需求是這樣的:
          只有一個(gè)索引;該索引必須是唯一索引。你一定看出來(lái)了,這就是典型的 KV 場(chǎng)景。
          由于沒(méi)有其他索引,所以也就不用考慮其他索引的葉子節(jié)點(diǎn)大小的問(wèn)題。這時(shí)候我們就要優(yōu)先考慮上一段提到的“盡量使用主鍵查詢”原則,直接將這個(gè)索引設(shè)置為主鍵,可以避免每次查詢需要搜索兩棵樹

          如何避免長(zhǎng)事務(wù)對(duì)業(yè)務(wù)的影響?
          首先,從應(yīng)用開發(fā)端來(lái)看:
          確認(rèn)是否使用了 set autocommit=0。這個(gè)確認(rèn)工作可以在測(cè)試環(huán)境中開展,把 MySQL 的 general_log 開起來(lái),然后隨便跑一個(gè)業(yè)務(wù)邏輯,通過(guò) general_log 的日志來(lái)確認(rèn)。一般框架如果會(huì)設(shè)置這個(gè)值,也就會(huì)提供參數(shù)來(lái)控制行為,你的目標(biāo)就是把它改成 1。
          確認(rèn)是否有不必要的只讀事務(wù)。比方說(shuō)把好幾個(gè) select 語(yǔ)句放到了事務(wù)中。這種只讀事務(wù)可以去掉。
          業(yè)務(wù)連接數(shù)據(jù)庫(kù)的時(shí)候,根據(jù)業(yè)務(wù)本身的預(yù)估,通過(guò) SET MAX_EXECUTION_TIME 命令,來(lái)控制每個(gè)語(yǔ)句執(zhí)行的最長(zhǎng)時(shí)間,避免單個(gè)語(yǔ)句意外執(zhí)行太長(zhǎng)時(shí)間

          從數(shù)據(jù)庫(kù)端來(lái)看:
          監(jiān)控 information_schema.Innodb_trx 表,設(shè)置長(zhǎng)事務(wù)閾值,超過(guò)就報(bào)警 / 或者 kill;
          Percona 的 pt-kill 這個(gè)工具不錯(cuò),推薦使用;
          在業(yè)務(wù)功能測(cè)試階段要求輸出所有的 general_log,分析日志行為提前發(fā)現(xiàn)問(wèn)題;
          如果使用的是 MySQL 5.6 或者更新版本,把 innodb_undo_tablespaces 設(shè)置成 2(或更大的值)。如果真的出現(xiàn)大事務(wù)導(dǎo)致回滾段過(guò)大,這樣設(shè)置后清理起來(lái)更方便

          最左前綴:聯(lián)合索引的最左 N 個(gè)字段,也可以是字符串索引的最左 M 個(gè)字符

          覆蓋索引

          在一個(gè)市民信息表上,是否有必要將身份證號(hào)和名字建立聯(lián)合索引?
          單從索引過(guò)濾的使用方面沒(méi)有必要,但是使用聯(lián)合索引,在根據(jù)身份證查詢姓名的時(shí)候可以不需要回表–這個(gè)是否需要?jiǎng)?chuàng)建冗余索引需要權(quán)衡

          在建立聯(lián)合索引的時(shí)候,如何安排索引內(nèi)的字段順序。
          第一原則是,如果通過(guò)調(diào)整順序,可以少維護(hù)一個(gè)索引,那么這個(gè)順序往往就是需要優(yōu)先考慮采用的。

          如果既有聯(lián)合查詢,又有基于 a、b 各自的查詢呢?
          查詢條件里面只有 b 的語(yǔ)句,是無(wú)法使用 (a,b) 這個(gè)聯(lián)合索引的,這時(shí)候你不得不維護(hù)另外一個(gè)索引,也就是說(shuō)你需要同時(shí)維護(hù) (a,b)、(b) 這兩個(gè)索引。
          這時(shí)候,我們要考慮的原則就是空間了。比如上面這個(gè)市民表的情況,name 字段是比 age 字段大的 ,那我就建議你創(chuàng)建一個(gè)(name,age) 的聯(lián)合索引和一個(gè) (age) 的單字段索引 (比 (age,name), (name)更節(jié)省空間)

          通過(guò)兩個(gè) alter 語(yǔ)句重建索引 k,以及通過(guò)兩個(gè) alter(先drop再add) 語(yǔ)句重建主鍵索引是否合理
          重建索引 k 的做法是合理的,可以達(dá)到省空間的目的(對(duì)于有頁(yè)分裂的數(shù)據(jù)進(jìn)行重新排序,節(jié)省空間)。但是,重建主鍵的過(guò)程不合理。不論是刪除主鍵還是創(chuàng)建主鍵,都會(huì)將整個(gè)表重建。所以連著執(zhí)行這兩個(gè)語(yǔ)句的話,第一個(gè)語(yǔ)句就白做了。這兩個(gè)語(yǔ)句,你可以用這個(gè)語(yǔ)句代替 : alter table T engine=InnoDB

          06 | 全局鎖和表鎖 :給表加個(gè)字段怎么有這么多阻礙?

          全局鎖和表鎖

          全局鎖

          **全局鎖就是對(duì)整個(gè)數(shù)據(jù)庫(kù)實(shí)例加鎖。**MySQL 提供了一個(gè)加全局讀鎖的方法,命令是 Flush tables with read lock (FTWRL)。當(dāng)你需要讓整個(gè)庫(kù)處于只讀狀態(tài)的時(shí)候,可以使用這個(gè)命令,之后其他線程的以下語(yǔ)句會(huì)被阻塞:數(shù)據(jù)更新語(yǔ)句(數(shù)據(jù)的增刪改)、數(shù)據(jù)定義語(yǔ)句(包括建表、修改表結(jié)構(gòu)等)和更新類事務(wù)的提交語(yǔ)句
          全局鎖的典型使用場(chǎng)景是,做全庫(kù)邏輯備份。也就是把整庫(kù)每個(gè)表都 select 出來(lái)存成文本。

          我們?cè)谇懊嬷v事務(wù)隔離的時(shí)候,其實(shí)是有一個(gè)方法能夠拿到一致性視圖的,對(duì)吧?是的,就是在可重復(fù)讀隔離級(jí)別下開啟一個(gè)事務(wù)

          官方自帶的邏輯備份工具是 mysqldump。當(dāng) mysqldump 使用參數(shù)–single-transaction 的時(shí)候,導(dǎo)數(shù)據(jù)之前就會(huì)啟動(dòng)一個(gè)事務(wù),來(lái)確保拿到一致性視圖。而由于 MVCC 的支持,這個(gè)過(guò)程中數(shù)據(jù)是可以正常更新的

          一致性讀是好,但前提是引擎要支持這個(gè)隔離級(jí)別。比如,對(duì)于 MyISAM 這種不支持事務(wù)的引擎,如果備份過(guò)程中有更新,總是只能取到最新的數(shù)據(jù),那么就破壞了備份的一致性。這時(shí),我們就需要使用 FTWRL 命令了

          既然要全庫(kù)只讀,為什么不使用 set global readonly=true 的方式呢?
          確實(shí) readonly 方式也可以讓全庫(kù)進(jìn)入只讀狀態(tài),但我還是會(huì)建議你用 FTWRL 方式,主要有兩個(gè)原因:
          一是,在有些系統(tǒng)中,readonly 的值會(huì)被用來(lái)做其他邏輯,比如用來(lái)判斷一個(gè)庫(kù)是主庫(kù)還是備庫(kù)。因此,修改 global 變量的方式影響面更大,不建議使用。

          二是,在異常處理機(jī)制上有差異。如果執(zhí)行 FTWRL 命令之后由于客戶端發(fā)生異常斷開,那么 MySQL 會(huì)自動(dòng)釋放這個(gè)全局鎖,整個(gè)庫(kù)回到可以正常更新的狀態(tài)。而將整個(gè)庫(kù)設(shè)置為 readonly 之后,如果客戶端發(fā)生異常,則數(shù)據(jù)庫(kù)就會(huì)一直保持 readonly 狀態(tài),這樣會(huì)導(dǎo)致整個(gè)庫(kù)長(zhǎng)時(shí)間處于不可寫狀態(tài),風(fēng)險(xiǎn)較高。

          表級(jí)鎖

          MySQL 里面表級(jí)別的鎖有兩種:一種是表鎖,一種是元數(shù)據(jù)鎖(meta data lock,MDL)。
          表鎖的語(yǔ)法是 lock tables … read/write
          lock tables 語(yǔ)法除了會(huì)限制別的線程的讀寫外,也限定了本線程接下來(lái)的操作對(duì)象。

          另一類表級(jí)的鎖是 MDL(metadata lock)
          MDL 不需要顯式使用,在訪問(wèn)一個(gè)表的時(shí)候會(huì)被自動(dòng)加上
          在 MySQL 5.5 版本中引入了 MDL,當(dāng)對(duì)一個(gè)表做增刪改查操作的時(shí)候,加 MDL 讀鎖;當(dāng)要對(duì)表做結(jié)構(gòu)變更操作的時(shí)候,加 MDL 寫鎖。

          雖然 MDL 鎖是系統(tǒng)默認(rèn)會(huì)加的,但卻是你不能忽略的一個(gè)機(jī)制。比如下面這個(gè)例子,我經(jīng)??吹接腥说舻竭@個(gè)坑里:給一個(gè)小表加個(gè)字段,導(dǎo)致整個(gè)庫(kù)掛了

          sessionC會(huì)由于sessionA和sessionB處于阻塞狀態(tài)。并且之后所有要在表 t 上新申請(qǐng) MDL 讀鎖的請(qǐng)求也會(huì)被 session C 阻塞(mysql Server端對(duì)于sessionC,D會(huì)有一個(gè) 隊(duì)列 來(lái)決定誰(shuí)先執(zhí)行,所以雖然session C的執(zhí)行被阻塞了,但是session D的執(zhí)行也會(huì)被session C阻塞)

          事務(wù)中的 MDL 鎖,在語(yǔ)句執(zhí)行開始時(shí)申請(qǐng),但是語(yǔ)句結(jié)束后并不會(huì)馬上釋放,而會(huì)等到整個(gè)事務(wù)提交后再釋放。

          如何安全地給小表加字段?

          首先我們要解決長(zhǎng)事務(wù),事務(wù)不提交,就會(huì)一直占著 MDL 鎖。

          在 MySQL 的 information_schema 庫(kù)的 innodb_trx 表中,你可以查到當(dāng)前執(zhí)行中的事務(wù)。如果你要做 DDL 變更的表剛好有長(zhǎng)事務(wù)在執(zhí)行,要考慮先暫停 DDL,或者 kill 掉這個(gè)長(zhǎng)事務(wù)。
          但考慮一下這個(gè)場(chǎng)景。如果你要變更的表是一個(gè)熱點(diǎn)表,雖然數(shù)據(jù)量不大,但是上面的請(qǐng)求很頻繁,而你不得不加個(gè)字段,你該怎么做呢?這時(shí)候 kill 可能未必管用,因?yàn)樾碌恼?qǐng)求馬上就來(lái)了。
          比較理想的機(jī)制是,在 alter table 語(yǔ)句里面設(shè)定等待時(shí)間,如果在這個(gè)指定的等待時(shí)間里面能夠拿到 MDL 寫鎖最好,拿不到也不要阻塞后面的業(yè)務(wù)語(yǔ)句,先放棄。之后開發(fā)人員或者 DBA 再通過(guò)重試命令重復(fù)這個(gè)過(guò)程。

          MariaDB 已經(jīng)合并了 AliSQL 的這個(gè)功能,所以這兩個(gè)開源分支目前都支持 DDL NOWAIT/WAIT n 這個(gè)語(yǔ)法。

          索引問(wèn)題:


          對(duì)于下面的語(yǔ)句
          select … from geek where c=N order by a
          走ca,cb索引都能定位到滿足c=N主鍵而且主鍵的聚簇索引本身就是按order by a,b排序,無(wú)序重新排序。
          所以ca可以去掉
          select … from geek where c=N order by b 這條sql如果只有 c單個(gè)字段的索引,定位記錄可以走索引,但是order by b的順序與主鍵順序不一致,需要額外排序cb索引可以把排序優(yōu)化

          課后問(wèn)題 : 當(dāng)備庫(kù)用–single-transaction 做邏輯備份的時(shí)候,如果從主庫(kù)的 binlog 傳來(lái)一個(gè) DDL 語(yǔ)句會(huì)怎么樣?

          Q1:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
          Q2:START TRANSACTION WITH CONSISTENT SNAPSHOT;
          /* other tables /
          Q3:SAVEPOINT sp;
          / 時(shí)刻 1 /
          Q4:show create table t1;
          / 時(shí)刻 2 /
          Q5:SELECT * FROM t1;
          / 時(shí)刻 3 /
          Q6:ROLLBACK TO SAVEPOINT sp;
          / 時(shí)刻 4 /
          / other tables */

          在備份開始的時(shí)候,為了確保 RR(可重復(fù)讀)隔離級(jí)別,再設(shè)置一次 RR 隔離級(jí)別 (Q1);
          啟動(dòng)事務(wù),這里用 WITH CONSISTENT SNAPSHOT 確保這個(gè)語(yǔ)句執(zhí)行完就可以得到一個(gè)一致性視圖(Q2);
          設(shè)置一個(gè)保存點(diǎn),這個(gè)很重要(Q3);
          show create 是為了拿到表結(jié)構(gòu) (Q4),然后正式導(dǎo)數(shù)據(jù) (Q5),
          回滾到 SAVEPOINT sp,在這里的作用是釋放 t1 的 MDL 鎖 (Q6)。
          當(dāng)然這部分屬于“超綱”,上文正文里面都沒(méi)提到。DDL 從主庫(kù)傳過(guò)來(lái)的時(shí)間按照效果不同,我打了四個(gè)時(shí)刻。題目設(shè)定為小表,我們假定到達(dá)后,如果開始執(zhí)行,則很快能夠執(zhí)行完成

          1)如果在 Q4 語(yǔ)句執(zhí)行之前到達(dá),現(xiàn)象:沒(méi)有影響,備份拿到的是 DDL 后的表結(jié)構(gòu)。
          2)如果在“時(shí)刻 2”到達(dá),則表結(jié)構(gòu)被改過(guò),Q5 執(zhí)行的時(shí)候,報(bào) Table definition has changed, please retry transaction,現(xiàn)象:mysqldump 終止;
          3)如果在“時(shí)刻 2”和“時(shí)刻 3”之間到達(dá),mysqldump 占著 t1 的 MDL 讀鎖,binlog 被阻塞,現(xiàn)象:主從延遲,直到 Q6 執(zhí)行完成。
          4)從“時(shí)刻 4”開始,mysqldump 釋放了 MDL 讀鎖,現(xiàn)象:沒(méi)有影響,備份拿到的是 DDL 前的表結(jié)構(gòu)。

          07 | 行鎖功過(guò):怎么減少行鎖對(duì)性能的影響?

          在 InnoDB 事務(wù)中,行鎖是在需要的時(shí)候才加上的,但并不是不需要了就立刻釋放,而是要等到事務(wù)結(jié)束時(shí)才釋放。這個(gè)就是兩階段鎖協(xié)議

          怎么解決由這種熱點(diǎn)行更新導(dǎo)致的性能問(wèn)題呢?問(wèn)題的癥結(jié)在于,死鎖檢測(cè)要耗費(fèi)大量的 CPU 資源。
          一種頭痛醫(yī)頭的方法,就是如果你能確保這個(gè)業(yè)務(wù)一定不會(huì)出現(xiàn)死鎖,可以臨時(shí)把死鎖檢測(cè)關(guān)掉
          另一個(gè)思路是控制并發(fā)度。
          這個(gè)并發(fā)控制要做在數(shù)據(jù)庫(kù)服務(wù)端。如果你有中間件,可以考慮在中間件實(shí)現(xiàn);如果你的團(tuán)隊(duì)有能修改 MySQL 源碼的人,也可以做在 MySQL 里面?;舅悸肪褪?#xff0c;對(duì)于相同行的更新,在進(jìn)入引擎之前排隊(duì)。這樣在 InnoDB 內(nèi)部就不會(huì)有大量的死鎖檢測(cè)工作了。

          如果團(tuán)隊(duì)里暫時(shí)沒(méi)有數(shù)據(jù)庫(kù)方面的專家,不能實(shí)現(xiàn)這樣的方案,能不能從設(shè)計(jì)上優(yōu)化這個(gè)問(wèn)題呢?
          你可以考慮通過(guò)將一行改成邏輯上的多行來(lái)減少鎖沖突。還是以影院賬戶為例,可以考慮放在多條記錄上,比如 10 個(gè)記錄,影院的賬戶總額等于這 10 個(gè)記錄的值的總和。這樣每次要給影院賬戶加金額的時(shí)候,隨機(jī)選其中一條記錄來(lái)加。這樣每次沖突概率變成原來(lái)的 1/10,可以減少鎖等待個(gè)數(shù),也就減少了死鎖檢測(cè)的 CPU 消耗。

          減少死鎖的主要方向,就是控制訪問(wèn)相同資源的并發(fā)事務(wù)量。

          課后問(wèn)題: 怎么刪除表的前 10000 行。=
          在一個(gè)連接中循環(huán)執(zhí)行 20 次 delete from T limit 500效率會(huì)比較好。確實(shí)是這樣的,第二種方式是相對(duì)較好的。
          第一種方式(即:直接執(zhí)行 delete from T limit 10000)里面,單個(gè)語(yǔ)句占用時(shí)間長(zhǎng),鎖的時(shí)間也比較長(zhǎng);而且大事務(wù)還會(huì)導(dǎo)致主從延遲。
          第三種方式(即:在 20 個(gè)連接中同時(shí)執(zhí)行 delete from T limit 500),會(huì)人為造成鎖沖突。

          08 | 事務(wù)到底是隔離的還是不隔離的?

          begin/start transaction 命令并不是一個(gè)事務(wù)的起點(diǎn),在執(zhí)行到它們之后的第一個(gè)操作 InnoDB 表的語(yǔ)句,事務(wù)才真正啟動(dòng)。如果你想要馬上啟動(dòng)一個(gè)事務(wù),可以使用 start transaction with consistent snapshot 這個(gè)命令。
          第一種啟動(dòng)方式,一致性視圖是在執(zhí)行第一個(gè)快照讀語(yǔ)句時(shí)創(chuàng)建的;
          第二種啟動(dòng)方式,一致性視圖是在執(zhí)行 start transaction with consistent snapshot 時(shí)創(chuàng)建的。

          在 MySQL 里,有兩個(gè)“視圖”的概念:
          一個(gè)是 view。它是一個(gè)用查詢語(yǔ)句定義的虛擬表,在調(diào)用的時(shí)候執(zhí)行查詢語(yǔ)句并生成結(jié)果。創(chuàng)建視圖的語(yǔ)法是 create view … ,而它的查詢方法與表一樣。
          另一個(gè)是 InnoDB 在實(shí)現(xiàn) MVCC 時(shí)用到的一致性讀視圖,即 consistent read view,用于支持 RC(Read Committed,讀提交)和 RR(Repeatable Read,可重復(fù)讀)隔離級(jí)別的實(shí)現(xiàn)。
          它沒(méi)有物理結(jié)構(gòu),作用是事務(wù)執(zhí)行期間用來(lái)定義“我能看到什么數(shù)據(jù)”
          我們這里說(shuō)的是第二種

          InnoDB 里面每個(gè)事務(wù)有一個(gè)唯一的事務(wù) ID,叫作 transaction id。它是在事務(wù)開始的時(shí)候向 InnoDB 的事務(wù)系統(tǒng)申請(qǐng)的,是按申請(qǐng)順序嚴(yán)格遞增的。
          而每行數(shù)據(jù)也都是有多個(gè)版本的。每次事務(wù)更新數(shù)據(jù)的時(shí)候,都會(huì)生成一個(gè)新的數(shù)據(jù)版本,并且把 transaction id 賦值給這個(gè)數(shù)據(jù)版本的事務(wù) ID,記為 row trx_id。同時(shí),舊的數(shù)據(jù)版本要保留,并且在新的數(shù)據(jù)版本中,能夠有信息可以直接拿到它。
          也就是說(shuō),數(shù)據(jù)表中的一行記錄,其實(shí)可能有多個(gè)版本 (row),每個(gè)版本有自己的 row trx_id。

          圖中的三個(gè)虛線箭頭,就是 undo log;而 V1、V2、V3 并不是物理上真實(shí)存在的,而是每次需要的時(shí)候根據(jù)當(dāng)前版本和 undo log 計(jì)算出來(lái)的。比如,需要 V2 的時(shí)候,就是通過(guò) V4 依次執(zhí)行 U3、U2 算出來(lái)

          因此,一個(gè)事務(wù)只需要在啟動(dòng)的時(shí)候聲明說(shuō),“以我啟動(dòng)的時(shí)刻為準(zhǔn),如果一個(gè)數(shù)據(jù)版本是在我啟動(dòng)之前生成的,就認(rèn);如果是我啟動(dòng)以后才生成的,我就不認(rèn),我必須要找到它的上一個(gè)版本

          在實(shí)現(xiàn)上, InnoDB 為每個(gè)事務(wù)構(gòu)造了一個(gè)數(shù)組,用來(lái)保存這個(gè)事務(wù)啟動(dòng)瞬間,當(dāng)前正在“活躍”的所有事務(wù) ID?!盎钴S”指的就是,啟動(dòng)了但還沒(méi)提交

          數(shù)組里面事務(wù) ID 的最小值記為低水位,當(dāng)前系統(tǒng)里面已經(jīng)創(chuàng)建過(guò)的事務(wù) ID 的最大值加 1 記為高水位。
          這個(gè)視圖數(shù)組和高水位,就組成了當(dāng)前事務(wù)的一致性視圖(read-view)

          當(dāng)開啟事務(wù)時(shí),需要保存活躍事務(wù)的數(shù)組(A),然后獲取高水位(B)。兩個(gè)動(dòng)作之間(A和B之間)是在事務(wù)系統(tǒng)的鎖保護(hù)下做的,可以認(rèn)為是原子性的,不會(huì)產(chǎn)生新事務(wù)

          如果落在黃色部分,那就包括兩種情況
          a. 若 row trx_id 在數(shù)組中,表示這個(gè)版本是由還沒(méi)提交的事務(wù)生成的,不可見;
          b. 若 row trx_id 不在數(shù)組中,表示這個(gè)版本是已經(jīng)提交了的事務(wù)生成的,可見(比方說(shuō)開啟事務(wù)11后,又開啟了事務(wù)12并快速提交,此時(shí)當(dāng)事務(wù)11執(zhí)行SQL生成一致性快照的時(shí)候就會(huì)出現(xiàn)黃色區(qū)域的b情形)

          InnoDB 利用了“所有數(shù)據(jù)都有多個(gè)版本”的這個(gè)特性,實(shí)現(xiàn)了“秒級(jí)創(chuàng)建快照”的能力 (這個(gè)快照本質(zhì)上就是上面的高低水位圖,創(chuàng)建非??焖?#xff09;

          當(dāng)它要去更新數(shù)據(jù)的時(shí)候,就不能再在歷史版本上更新了,否則事務(wù) C 的更新就丟失了。因此,事務(wù) B 此時(shí)的 set k=k+1 是在(1,2)的基礎(chǔ)上進(jìn)行的操作。
          所以,這里就用到了這樣一條規(guī)則:更新數(shù)據(jù)都是先讀后寫的,而這個(gè)讀,只能讀當(dāng)前的值,稱為“當(dāng)前讀”(current read)。

          當(dāng)前讀: 除了 update 語(yǔ)句外,select 語(yǔ)句如果加鎖,也是當(dāng)前讀(不過(guò)注意這個(gè)當(dāng)前讀需要獲取鎖,只有當(dāng)本事務(wù)獲取到對(duì)應(yīng)的鎖資源之后才可以讀取當(dāng)前最新的數(shù)據(jù)))

          事務(wù)的可重復(fù)讀的能力是怎么實(shí)現(xiàn)的?
          可重復(fù)讀的核心就是一致性讀(consistent read);而事務(wù)更新數(shù)據(jù)的時(shí)候,只能用當(dāng)前讀。如果當(dāng)前的記錄的行鎖被其他事務(wù)占用的話,就需要進(jìn)入鎖等待。

          超強(qiáng)干貨來(lái)襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生

          總結(jié)

          以上是生活随笔為你收集整理的《MySQL实战45讲》基础理论篇 1-8讲 学习笔记的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

          如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。