<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          關(guān)系型數(shù)據(jù)庫的瓶頸 與 優(yōu)化

          共 6041字,需瀏覽 13分鐘

           ·

          2021-09-03 22:58

          1. 數(shù)據(jù)庫的分類

          數(shù)據(jù)庫大致可以分為兩部分:

          1. 傳統(tǒng)的關(guān)系型數(shù)據(jù)庫, 如: MySQL, Oracle, SQLServer 以及 PostgreSQL; MySQL 是國內(nèi)使用最廣泛的數(shù)據(jù)庫, Oracle 在傳統(tǒng)行業(yè)應(yīng)用最為廣泛, PostgreSQL 性能和功能都比較完善, 但目前文檔和社區(qū)還有待成長.

          2. 非關(guān)系型數(shù)據(jù)庫, 如 HBase(列式數(shù)據(jù)庫), MongoDB(文檔型數(shù)據(jù)庫), Redis(高性能 KV 存儲), Lucene(搜索引擎) 等等.

          2. 關(guān)系型數(shù)據(jù)庫的瓶頸與優(yōu)化

          2.1 為什么數(shù)據(jù)庫的架構(gòu)需要調(diào)整

          1. 互聯(lián)網(wǎng)的數(shù)據(jù)增長往往是指數(shù)型的;

          2. 讀寫分離, 分布式: 單機(jī)性能上存在瓶頸;

          3. NoSQL, 搜索引擎: 特殊場景的需求無法滿足;

          4. 分析系統(tǒng): 無法滿足大數(shù)據(jù)的分析需求;

          5. 部署要求: 同城容災(zāi)/異地容災(zāi).

          2.2 數(shù)據(jù)庫會遇到什么問題

          2.2.1 性能

          • 查詢性能

          • 寫入更新

          • 并發(fā), 數(shù)據(jù)量等

          2.2.2 功能

          • 新功能: LBS/JSON/特殊業(yè)務(wù)場景

          • 數(shù)據(jù)安全性: 強(qiáng)一致性/非強(qiáng)一致性

          • 大數(shù)據(jù)分析

          • 搜索等

          3. 不同業(yè)務(wù)場景的存儲選型

          3.1 一個簡單的問題

          MySQL 已經(jīng)有 cache 了, 為何還需要加一層 Redis

          3.2 數(shù)據(jù)庫查詢開銷

          其中比較耗時的步驟有:

          • 建立 TCP 連接

          • 生成執(zhí)行計劃

          • 開表

          • 從磁盤掃描數(shù)據(jù)

          • 關(guān)閉連接

          3.2.1 SQL 解析

          假設(shè)有如下三條語句, 均是根據(jù)主鍵的查詢.

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          # 1 
          SELECT id, name, price FROM products WHERE id IN (1, 2, 3, 4, ... 30000); # (1-2s)

          # 2. 將第一條查詢轉(zhuǎn)換成 30000 條語句
          SELECT id, name, price FROM products WHERE id = 1;
          ...
          SELECT id, name, price FROM products WHERE id = 30000; # (2-3s)

          # 3. 將第一條轉(zhuǎn)換成 OR 語句
          SELECT id, name, price FROM products WHERE id = 1 OR id = 2 OR ... OR id = 30000; # (8-10s)

          造成第三條語句執(zhí)行時間如此長的主要原因就是大量的 OR 語句會導(dǎo)致 SQL 解析非常耗時.

          3.2.2 以 MySQL 的 InnoDB 存儲引擎主鍵查詢?yōu)槔?/span>

          1
          SELECT * FROM t WHERE id = ?;

          常規(guī)配置的服務(wù)器基本可以達(dá)到 400000 QPS.

          3.2.3 如果查詢條件不是主鍵

          1
          SELECT * FROM t WHERE name = ?;

          對于非主鍵的查詢, MySQL 會根據(jù)二級索引查詢到主索引對應(yīng)節(jié)點(diǎn)的位置. 按照圖中的情況, 會首先通過三次 IO 找到對應(yīng)主鍵, 在二級索引的葉子節(jié)點(diǎn)會同時保存索引字段的值以及主鍵的值, 再回到主索引通過主鍵查詢到整條記錄.

          在 MySQL 中, 主鍵查詢是最為高效的一類查詢.

          DBA 往往希望所有的 SQL 語句都是 KV 查詢, 但是往往是不現(xiàn)實(shí)的.

          • 主鍵查詢有限, 有些主鍵沒有業(yè)務(wù)含義;

          • 設(shè)計表結(jié)構(gòu)時, 并沒有考慮過主鍵問題.

          SQL 語句允許開發(fā)人員用各種方式從表中獲取數(shù)據(jù), 但 DBA 卻不會希望我們這么做.

          3.2.3 數(shù)據(jù)庫的大字段

          1
          content varchar(2046) NOT NULL COMMENT '原始消息';

          以 InnoDB 存儲引擎為例:

          • TinyText/Text/Mediumtext

          • varchar(256)/varchar(500)/varchar(20000)

          • tinyBlob/blob/mediumBlob

          text 類型本質(zhì)上和 varchar 類型沒有區(qū)別.

          MySQL 中, 數(shù)據(jù)是以頁的方式來組織的, 每個數(shù)據(jù)頁默認(rèn)大小 16 KB, 其中包括頁頭, 頁尾, 中間是一行一行的記錄.

          圖中的每條記錄包括 ID, NAME, AGE 和 DETAIL. 假設(shè) DETAIL 是一個大字段, 達(dá)到超過了單頁的大小, 此時 DB 會新開一個數(shù)據(jù)頁, 當(dāng)前頁通過指針指向該頁. 如果一頁依然不夠, MySQL 就會不斷新加數(shù)據(jù)頁直到能夠存下為止.

          一旦存在這樣的大字段, 會帶來如下問題:

          1. 查詢開銷大;

          2. 查詢影響大, 嚴(yán)重時會觸發(fā)熱頁換出, 引起系統(tǒng)抖動. MySQL 將記錄從磁盤讀取出來的時候, 可能會有很多數(shù)據(jù)頁, MySQL 自帶緩存時非常寶貴的, 會導(dǎo)致真正使用頻率高的數(shù)據(jù)頁被替換成大字段的數(shù)據(jù)頁. 此外, 對 MySQL 來說, 即便只查記錄中的某幾個字段, 數(shù)據(jù)庫依然會把整條記錄取出, 讀進(jìn)內(nèi)存, 再進(jìn)行指定字段的篩選

          對于大字段場景可以嘗試的優(yōu)化方案:

          1. 是否適合存儲關(guān)系型數(shù)據(jù)庫;

          2. 是否所有數(shù)據(jù)都需要存數(shù)據(jù)庫;

          3. 是否可以新建一張表存儲大字段.

          3.2.4 數(shù)據(jù)庫緩存利用率

          以 InnoDB 存儲引擎為例:

          1. MySQL 默認(rèn)數(shù)據(jù)頁為 16KB, 哪怕只讀一行記錄, 也需要從磁盤中取出 16KB 數(shù)據(jù)取出;

          2. MySQL 是以頁為最小的緩存單位;

          3. 如果每行數(shù)據(jù) 1kb, 256kb 內(nèi)存空間能緩存多少行有效數(shù)據(jù), 最好的情況是每條數(shù)據(jù)整齊排列在一個數(shù)據(jù)頁中, 那么可以緩存256條記錄, 最壞的情況下每一頁只存在一條數(shù)據(jù), 那么就只能緩存16條;

          4. 在 256KB 的 Buffer Pool 中, 并不是所有空間都用來做數(shù)據(jù)頁緩存, 有很大的一塊在 Write Buffer(MySQL 為了優(yōu)化寫操作, 會將一段時間內(nèi)的寫操作先放在 Write Buffer, 再由后臺線程定時異步刷新到磁盤上). 然而剩下的 128KB 中還存在一部分臟頁.

          緩存為什么如此重要:

          • 互聯(lián)網(wǎng)產(chǎn)品往往讀多寫少;

          • 擴(kuò)展緩存遠(yuǎn)比擴(kuò)展 DB 簡單;

          • 數(shù)據(jù)庫緩存利用率很低;

          • 互聯(lián)網(wǎng)應(yīng)用對 DB 響應(yīng)時間比較敏感, 緩存系統(tǒng)一般性能比較好

          • 只要符合條件的數(shù)據(jù)都應(yīng)該走緩存:

            1. 修改不頻繁的數(shù)據(jù);

            2. 非實(shí)時的數(shù)據(jù), 一致性要求不嚴(yán)的數(shù)據(jù);

            3. 查詢頻率較高, 帶有明顯熱點(diǎn)請求的數(shù)據(jù);

          3.2.5 緩存帶來的問題

          用了緩存并不一定代表沒有問題

          1. 緩存命中

          2. 緩存穿透

          3. 緩存失效

          4. 緩存一致

          3.2.6 選擇正確的索引

          降低掃描數(shù)據(jù)量還是降低排序代價

          大多數(shù)查詢只能使用一個索引, 因此在需要對多個列進(jìn)行操作的 SQL 語句中, 我們需要準(zhǔn)確評估每個索引的開銷.

          • key idx_create_time(createTime)

          • key idx_price(price)

            1
            SELECT * FROM tb_order WHERE createTime > xxx AND createTime < xxx ORDER BY price DESC;

          3.2.7 索引的使用

          3.2.7.1 索引字段過長, 超過索引支持

          1
          2
          3
          4
          # name varchar(512)
          # ket idx_name(name(100))

          SELECT * FROM comment WHERE name >= 'destiny' ORDER BY name ASC LIMIT 100;

          上面的例子在實(shí)際場景中執(zhí)行非常慢, 使用 EXPLAIN 打印查詢計劃:

            select_type: SIMPLE
          table: comment
          type: range
          possible_keys: id_name
          key: uk_sess
          key_len: 403
          ref: NULL
          rows: 462642
          Extra:Using where; Using filesort
          1 row in set(0.00sec)

          其中需要重點(diǎn)關(guān)注的是: Extra:Using where; Using filesort

          • Using where: 表用到了索引

          • Using filesort: MySQL 自帶的磁盤排序, 并沒有用到索引的排序

          問題是為什么使用了索引, 查詢效率依然非常慢?

          真正的原因是字段太長, 而索引的長度只能覆蓋 256 字節(jié), 導(dǎo)致 ORDER BY 無法在內(nèi)存中完成排序

          3.2.7.2

          查詢某個用戶 id 的分值總和

          1
          2
          3
          4
          5
          6
          -- uid varchar(190) NOT NULL DEFAULT '' COMMENT '用戶 id',
          -- score bigint(20) NOT NULL DEFAULT '0' COMMENT '變動分值, 正增, 負(fù)減',
          -- primary key ID
          -- KEY idx_uid(uid)

          SELECT SUM(score) FROM name WHERE uid = '5993156'

          這條 SQL 的執(zhí)行順序:

          1. 根據(jù)二級索引 uid 找到所有主鍵 id

          2. 再根據(jù)主鍵逐行找到 score

          3. 對 score 進(jìn)行聚合

          這個 SQL 的問題在于需要進(jìn)行大量的回表操作(從二級索引回到一級索引), 然后將全部符合過濾條件的記錄放在內(nèi)存中完成聚合操作.

          改進(jìn)的方法其實(shí)很簡單, 可以嘗試使用 (uid, score) 建立聯(lián)合索引, 這樣只需要查詢二級索引就可以獲得全部數(shù)據(jù).

          隨機(jī)插入 100W 條數(shù)據(jù), 現(xiàn)在對比下兩條索引的開銷.

          3.3 數(shù)據(jù)庫寫開銷

          • 對持久化要求嚴(yán)格, 寫操作代價大

          • 日志文件需要 fsync, 硬件存在瓶頸

          • 數(shù)據(jù)庫寫操作很難擴(kuò)展

          • 主從要求一致場景下還要算上網(wǎng)絡(luò)開銷

          1. 將 3 所在的數(shù)據(jù)頁讀到緩存中;

          2. 在內(nèi)存中將 3 改成 5, 提交事務(wù), 觸發(fā) Redo Log 的刷新;

          3. 向用戶返回操作成功;

          3.4 業(yè)務(wù)場景觸發(fā)的高并發(fā)寫入

          3.4.1 秒殺

          • 高并發(fā)寫入的極端情況

          • 業(yè)務(wù)優(yōu)化(緩存/令牌通/排隊(duì)/Java 信號量/樂觀鎖)

          • 熱點(diǎn)資源隔離

          • 引入數(shù)據(jù)庫線程池

          • InnoDB 內(nèi)核層優(yōu)化: AliSQL

          3.4.2 私信/站內(nèi)信消息推送

          • 高并發(fā)寫入

          • 伴隨大量的讀請求

          • 系統(tǒng)消息/個人消息區(qū)分對待

          • 消息內(nèi)容單獨(dú)對待

          • 延遲寫入, 通過隊(duì)列/緩存達(dá)到限流目的

          3.4.3 聽歌量

          • 業(yè)務(wù)原因?qū)е聦懭肓糠浅4?/span>

          • 插入更新比不確定, 更新能力強(qiáng)

          • 數(shù)據(jù)庫需要具備自動擴(kuò)展的能力

          • 數(shù)據(jù)非強(qiáng)一致

          3.5 死鎖和超時

          • InnoDB 鎖超時默認(rèn)需要 5s 等待

          • 死鎖馬上就能被發(fā)現(xiàn), 然后被 DB 自動回滾

          • 鎖超時一般是索引不對, 或者 SQL 語句執(zhí)行性能較差

          • 死鎖一般是業(yè)務(wù)實(shí)現(xiàn)有問題

          • 鎖超時一般影響較為可控

          • 死鎖情況比較嚴(yán)重, 會導(dǎo)致全站崩潰

          3.6 數(shù)據(jù)庫并發(fā)事務(wù), 鎖

          • 業(yè)務(wù)流程中的鎖: 減庫存, 發(fā)優(yōu)惠券

          悲觀鎖實(shí)現(xiàn):

          1
          2
          3
          4
          5
          BEGIN;
          SELECT count FROM tb WHERE id = ? FOR UPDATE;
          -- do sth
          UPDATE tb SET count = count - ? WHERE id = ?;
          COMMIT;

          樂觀鎖實(shí)現(xiàn):

          1
          2
          3
          4
          5
          BEGIN;
          SELECT count FROM tb WHERE id = ?;
          UPDATE tb SET count = count - ? WHERE id = ? AND count = :count;
          COMMIT;
          -- do sth

          4. 數(shù)據(jù)庫的模塊化拆分

          4.1 單機(jī)服務(wù)器的局限

          • 雖然硬件配置越來越高, 但是總有瓶頸(e.g. CPU/內(nèi)存/網(wǎng)絡(luò)/IO/容量)

          • 為了后續(xù)業(yè)務(wù)的可擴(kuò)展性

          • 單機(jī)系統(tǒng)崩潰風(fēng)險較高

          • 優(yōu)化性能

            • 讀寫分離

            • 冷熱分離, 創(chuàng)建歸檔庫

            • 關(guān)鍵鏈路和非關(guān)鍵鏈路隔離

            • 系統(tǒng)層面做好降級

          4.2 常見拆分方案

          4.2.1 讀寫分離

          讀寫分離的原理就是將數(shù)據(jù)庫讀寫操作分散到不同的節(jié)點(diǎn)上

          讀寫分離的基本原理就是:

          • 數(shù)據(jù)庫服務(wù)器搭建主從集群;

          • 數(shù)據(jù)庫主機(jī)負(fù)責(zé)寫操作, 從機(jī)只負(fù)責(zé)讀操作;

          • 數(shù)據(jù)庫主機(jī)通過復(fù)制將數(shù)據(jù)同步到從機(jī), 每臺數(shù)據(jù)庫服務(wù)器都存儲了所有業(yè)務(wù)數(shù)據(jù).

          • 業(yè)務(wù)服務(wù)器將寫操作發(fā)給數(shù)據(jù)庫主機(jī), 將讀操作發(fā)給數(shù)據(jù)庫從機(jī).

          使用讀寫分離之后, 可能會引入兩個問題:

          1. 主從復(fù)制延遲

          2. 分配機(jī)制

          4.2.1.1 復(fù)制延遲

          主從復(fù)制的延遲可能達(dá)到秒級, 如果有大量數(shù)據(jù)短時間需要完成同步, 延遲甚至可能達(dá)到分鐘.

          主從復(fù)制所帶來的問題:

          如果業(yè)務(wù)服務(wù)器將數(shù)據(jù)寫入到主庫后進(jìn)行讀取, 此時讀操作訪問從庫, 而主庫的數(shù)據(jù)沒有完全復(fù)制過來, 從庫是無法讀取到最新數(shù)據(jù)的.

          解決方案:

          1. 寫操作后的讀操作指定發(fā)給主庫, 邏輯會和業(yè)務(wù)強(qiáng)綁定, 對業(yè)務(wù)侵入較大.

          2. 讀從庫失敗后再讀一次主庫, 如果有大量沒有命中從庫的讀請求, 會給主庫帶來較大壓力.

          3. 關(guān)鍵業(yè)務(wù)讀寫操作全部走主庫, 非關(guān)鍵業(yè)務(wù)采用讀寫分離.

          4.2.1.2 分配機(jī)制

          將讀寫操作區(qū)分開來, 然后訪問不同的數(shù)據(jù)庫服務(wù)器, 一般有兩種方式: 程序代碼封裝和中間件封裝

          1. 程序代碼封裝

          在代碼中抽象一個數(shù)據(jù)訪問層, 實(shí)現(xiàn)讀寫操作分離和數(shù)據(jù)庫服務(wù)器連接的管理.

          特點(diǎn):

          1. 實(shí)現(xiàn)簡單, 可以根據(jù)業(yè)務(wù)定制化;

          2. 無法做到多語言通用, 容易重復(fù)開發(fā);

          3. 故障情況下, 如果主從發(fā)生切換, 需要將系統(tǒng)配置手動修改.

          2. 中間件封裝

          獨(dú)立一套系統(tǒng)出來, 實(shí)現(xiàn)讀寫分離和數(shù)據(jù)庫服務(wù)器連接的管理, 中間件對業(yè)務(wù)服務(wù)器提供 SQL 兼容的協(xié)議, 業(yè)務(wù)服務(wù)器無需自己進(jìn)行讀寫分離, 對于業(yè)務(wù)服務(wù)器來說, 訪問中間件和訪問數(shù)據(jù)庫沒有區(qū)別


          特點(diǎn):

          1. 能夠支持多種編程語言, 因?yàn)閿?shù)據(jù)庫中間件對業(yè)務(wù)提供的是標(biāo)準(zhǔn)的 SQL 接口.

          2. 實(shí)現(xiàn)較為復(fù)雜, 需要完整支持 SQL 語法和數(shù)據(jù)庫服務(wù)器的協(xié)議.

          3. 性能要求很高, 容易成為瓶頸.

          4. 數(shù)據(jù)庫主從切換對業(yè)務(wù)服務(wù)器無感知, 數(shù)據(jù)庫中間件可以探測數(shù)據(jù)庫服務(wù)器的主從狀態(tài)(e.g. 向某個測試庫寫入一條數(shù)據(jù), 成功的是主機(jī), 失敗的是從機(jī))

          4.2.2 分布式

          讀寫分離分散了讀寫操作的壓力, 但沒有分散存儲的壓力, 當(dāng)數(shù)據(jù)量達(dá)到千萬級以上的時候, 單臺數(shù)據(jù)庫服務(wù)器的存儲能力就會成為瓶頸:

          1. 數(shù)據(jù)量太大, 讀寫的性能會大幅下降.

          2. 數(shù)據(jù)文件備份和恢復(fù)都會很困難.

          • 垂直分表: 適合將某些表中不常用且占用大量空間的列拆分出去. 代價是操作表的數(shù)量增加.

          • 水平拆分: 適合行數(shù)較大的表, 會引入更多的復(fù)雜度: 路由join 操作count 操作 等


          https://destinywang.github.io/blog/2019/01/19/關(guān)系型數(shù)據(jù)庫的瓶頸與優(yōu)化

          喜歡,在看

          瀏覽 56
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级特黄BBBBBB视频免费观看 | 久久日本道| 中文字幕亚洲成人在线 | 俺去啦俺去也 | 婷婷丁香社区五月天 |