知識分享--資料庫

Shine-x發表於2020-03-25

MySQL中的索引

索引

索引常見的幾種型別

索引常見的型別有雜湊索引,有序陣列索引,二叉樹索引,跳錶等等。主要探討 MySQL 的預設儲存引擎 InnoDB 的索引結構。

InnoDB的索引結構

有兩種常用的索引, B+ 樹索引和Hash索引

為什麼使用B+Tree?

  • 索引的常見模型常見的有,雜湊表、有序陣列、搜尋樹。
  • 有序陣列,優點是等值查詢,範圍查詢都非常快,缺點也很明顯,就是插入效率太低,因為如果從中間插入,要移動後面所有的元素。
  • 在B+樹中是只有葉子結點會儲存資料,而且所有葉子結點會形成一個連結串列。而在InnoDB中維護的是一個雙向連結串列。

知識分享--資料庫

  • 雜湊結構只適用於等值查詢(但這樣速度非常快)。雜湊結構不支援順序檢索例如’<’、’>’、”between and”等,這種儲存結構屬於“鍵值”查詢,符合這種需求可以考慮使用雜湊索引。
  • 優化器不能使用雜湊索引來加快 order by 操作。(此型別的索引不能用於按順序搜尋下一個條目。
  • mysql 不能大致確定兩個值之間有多少行 (範圍優化器使用它來決定要使用哪個索引)。如果將 MyISAM 或 InnoDB 表更改為雜湊索引的記憶體表, 這可能會影響某些查詢。

為什麼使用 B+樹 而不使用二叉樹或者B樹?

首先,我們知道訪問磁碟需要訪問到指定塊中,而訪問指定塊是需要 碟片旋轉磁臂移動 的,這是一個比較耗時的過程,如果增加樹高那麼就意味著你需要進行更多次的磁碟訪問,所以會採用n叉樹。而使用B+樹是因為如果使用B樹在進行一個範圍查詢的時候每次都會進行重新檢索,而在B+樹中可以充分利用葉子結點的連結串列。

在建表的時候你可能會新增多個索引,而 InnDB 會為每個索引建立一個 B+樹 進行儲存索引

建立一個簡單的測試表

create table test(
  id int primary key,
  a int not null,
  name varchar,
  index(a)
)engine = InnoDB;

這個時候 InnDB 就會為我們建立兩個 B+索引樹

一個是 主鍵聚簇索引,另一個是 普通索引輔助索引,這裡引用 MySQL淺談(索引、鎖)

知識分享--資料庫

可以看到在輔助索引上面的葉子節點的值只是存了主鍵的值,而在主鍵的聚簇索引上的葉子節點才是存上了整條記錄的值

回表

這裡就會引申出一個概念叫回表,比如這個時候我們進行一個查詢操作

select name from test where a = 30;

因為條件 MySQL 是會走 a 的索引的,但是 a 索引上並沒有儲存 name 的值,此時我們就需要拿到相應 a 上的主鍵值,然後通過這個主鍵值去走 聚簇索引 最終拿到其中的name值,這個過程就叫回表。

我們來總結一下回表是什麼?MySQL在輔助索引上找到對應的主鍵值並通過主鍵值在聚簇索引上查詢所要的資料就叫回表

索引維護

  • 我們知道索引是需要佔用空間的,索引雖能提升我們的查詢速度但是也是不能濫用。
  • 比如我們在使用者表裡用身份證號做主鍵,那麼每個二級索引的葉子節點佔用約20個位元組,而如果用整型做主鍵,則只要4個位元組,如果是長整型(bigint)則是8個位元組。也就是說如果我用整型後面維護了4個g的索引列表,那麼用身份證將會是20個g。
  • 所以我們可以通過縮減索引的大小來減少索引所佔空間
  • 當然B+樹為了維護索引的有序性會在刪除,插入的時候進行一些必要的維護(在InnoDB中刪除會將節點標記為“可複用”以減少對結構的變動)。
  • 比如在增加一個節點的時候可能會遇到資料頁滿了的情況,這個時候就需要做頁的分裂,這是一個比較耗時的工作,而且頁的分裂還會導致資料頁的利用率變低,比如原來存放三個資料的資料頁再次新增一個資料的時候需要做頁分裂,這個時候就會將現有的四個資料分配到兩個資料頁中,這樣就減少了資料頁利用率。

覆蓋索引

有時候我們查輔助索引的時候就已經滿足了我們需要查的資料,這個時候 InnoDB 就會進行一個叫 覆蓋索引 的操作來提升效率,減少回表。

比如這個時候我們進行一個 select 操作

select id from test where a = 1;

這個時候很明顯我們走了 a 的索引直接能獲取到 id 的值,這個時候就不需要進行回表,我們這個時候就使用了 覆蓋索引

簡單來說 覆蓋索引 就是當我們走輔助索引的時候能獲取到我們所需要的資料的時候不需要再次進行回表操作的操作

聯合索引

新建一個學生表

CREATE TABLE `stu` (
  `id` int(11) NOT NULL,
  `class` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `class_name` (`class`,`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

假如這個時候我們有一個需求,我們需要通過班級號去找對應的學生姓名,我們使用 class(班級號) 和 name 做一個 聯合索引。

select name from stu where class = 102;

這個時候我們就可以直接在 輔助索引 上查詢到學生姓名而不需要再次回表。

總的來說,設計好索引,充分利用覆蓋索引能很大提升檢索速度

最左字首原則

聯合索引 作為基礎的,是一種聯合索引的匹配規則。

我們有個學生遲到,但是他在門衛記錄資訊的時候只寫了自己的名字張三而沒有寫班級,所以我們需要通過學生姓名去查詢相應的班級號。

select class from stu where name = '張三';

這個時候因為最左匹配原則我們就不會走我們的聯合索引了,而是進行了全表掃描

知識分享--資料庫

我們可以看到整個索引設計就是這麼設計的,所以我們需要查詢的時候也需要遵循著這個規則,如果我們直接使用name,那麼InnoDB是不知道我們需要幹什麼的。

當然最左匹配原則還有這些規則

  • 全值匹配的時候優化器會改變順序,也就是說你全值匹配時的順序和原先的聯合索引順序不一致沒有關係,優化器會幫你調好。
  • 索引匹配從最左邊的地方開始,如果沒有則會進行全表掃描,比如你設計了一個(a,b,c)的聯合索引,然後你可以使用(a),(a,b),(a,b,c) 而你使用 (b),(b,c),(c)就用不到索引了。
  • 遇到範圍匹配會取消索引。比如這個時候你進行一個這樣的 select 操作
select * from stu where class > 100 and name = '張三';

這個時候 InnoDB 就會放棄索引而進行全表掃描,因為這個時候 InnoDB 會不知道怎麼進行遍歷索引,所以進行全表掃描。

索引下推

剛剛的操作在 MySQL5.6 版本以前是需要進行回表的,但是5.6之後的版本做了一個叫 索引下推 的優化。

select * from stu where class > 100 and name = '張三';

如何優化的呢?因為剛剛的最左匹配原則我們放棄了索引,後面我們緊接著會通過回表進行判斷 name,這個時候我們所要做的操作應該是這樣的

知識分享--資料庫

但是有了索引下推之後就變成這樣了,此時 “李四” 和 “小明” 這兩個不會再進行回表。

知識分享--資料庫

因為這裡匹配了後面的name = 張三,也就是說,如果最左匹配原則因為範圍查詢終止了,InnoDB還是會索引下推來優化效能。

查詢優化器

一條SQL語句的查詢,可以有不同的執行方案,至於最終選擇哪種方案,需要通過優化器進行選擇,選擇執行成本最低的方案。 在一條單表查詢語句真正執行之前,MySQL的查詢優化器會找出執行該語句所有可能使用的方案,對比之後找出成本最低的方案。這個成本最低的方案就是所謂的執行計劃。 優化過程大致如下: 1、根據搜尋條件,找出所有可能使用的索引 2、計算全表掃描的代價 3、計算使用不同索引執行查詢的代價 4、對比各種執行方案的代價,找出成本最低的那一個

一些最佳實踐

哪些情況需要建立索引?
  • 頻繁作為查詢條件的欄位應建立索引。
  • 多表關聯查詢的時候,關聯欄位應該建立索引。
  • 查詢中的排序欄位,應該建立索引。
  • 統計或者分組欄位需要建立索引。
哪些情況不需要建立索引
  • 表記錄少。
  • 經常增刪改查的表。
  • 頻繁更新的欄位。
  • where 條件使用不高的欄位。
  • 欄位很大的時候。
其他
  • 儘量選擇區分度高的列作為索引。
  • 不要對索引進行一些函式操作,還應注意隱式的型別轉換和字元編碼轉換。
  • 儘可能的擴充套件索引,不要新建立索引。比如表中已經有了a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可。
  • 多考慮覆蓋索引,索引下推,最左匹配。

MySQL備份

邏輯備份

邏輯備份用於備份資料庫的結構(CREAET DATABASE、CREATE TABLE)和資料(INSERT),這種備份型別適合資料量小、跨SQL伺服器、需要修改資料等場景。如mysqldump命令就是產生一個邏輯備份工具,使用mysqldump輸出的檔案包含CREATE TABLEINSERT語句,能夠直接重建表內容和表結構。

使用邏輯備份有以下優勢和劣勢:

優勢

  • 可移植性高,SQL語句可直接適用於其他SQL伺服器;
  • 在資料恢復之前可增加、修改資料;
  • 資料恢復粒度小可以是伺服器、資料庫、表級別;
  • 使用文字格式,可讀性高;

劣勢

  • 備份時需要訪問mysql伺服器,影響其他客戶端;
  • 需要將資料轉換成邏輯格式(SQL,CSV);
  • 如果命令執行在客戶端,mysql伺服器還需要將資料傳送給客戶端;
  • 因為輸出格式為文字檔案,佔用空間較大;

物理備份

物理備份是包括儲存資料庫內容的目錄和檔案的副本,這種型別的備份適用於需要在出現問題時快速恢復的大型重要資料庫。

優勢

  • 完整的Mysql檔案和目錄備份,只需要複製檔案不需要轉換,速度比邏輯備份更快;
  • 除了備份資料,還能備份配置檔案和日誌檔案;
  • 不需要執行Mysql伺服器就可以完成備份;
  • 備份工具簡單使用cp、scp、tar命令即可完成備份;

劣勢

  • 可移植性不高,恢復資料只適用於相同或類似的機器上;
  • 為了保持資料庫檔案的一致性,需要停機備份;
  • 恢復粒度不能按表或使用者恢復;

線上備份和離線備份

線上備份需要mysql伺服器處理執行狀態,以便備份工具從mysql伺服器中獲取資料。離線備份表示mysql伺服器處理停止狀態。兩種備份形式也可以稱為“熱備份”和“冷備份“。

線上備份的主要特性

  • 備份不需要停機,對其他客戶端影響較小其他連線能夠正常訪問mysql伺服器(依賴操作型別,如讀操作);
  • 備份需要加鎖,以免在備份期間對資料做出修改;

離線備份的主要特性

  • 備份期間伺服器不可用;
  • 備份過程更簡單,不會受到客戶端的干擾;

邏輯備份(mysqldump使用)

mysqldump屬於邏輯備份命令,使用mysqldump備份的優勢是它非常方便和靈活,可以直接編輯輸出檔案或者使用匯入到其他的SQL伺服器中去,但是它不能用作備份大量資料的快速解決方案,對於大資料量,即使備份花費的時候可以接受,但是恢復資料也可能會非常緩慢,因為執執行SQL語句會涉及磁碟I/O進行插入,建立索引等。

mysqldump的使用方式非常簡單:

shell> mysqldump [options] db_name [tbl_name ...]
shell> mysqldump [options] --databases db_name ...
shell> mysqldump [options] --all-databases

使用mysqldump備份時要注意:資料庫的一致狀態,在執行mysqldump命令時要保證資料不會再發生變更,保持資料的一致性有二種方法:

  • 使Mysql伺服器只讀
  • 使用事務加上隔離級別:REPEATABLE READ

使用REPEATABLE READ事務隔離級別執行mysqldump命令(使用事務保持資料庫的一致狀態):

mysqldump --master-data=2 \
 --flush-logs  \
 --single-transaction  \
 --all-databases > /backup/`date +%F-%H`-mysql-all.sql  

備份引數說明:

  • –master-data: 將二進位制日誌檔案的名稱和位置備份
  • –flush-logs: 開始備份之前重新整理mysql伺服器日誌檔案
  • –single-transaction:開始備份之前設定事務隔離級別為REPEATABLE READ然後傳送一個START TRANSACTION命令。
  • –all-databases:備份所有資料庫

物理備份(複製原始檔案)

為了保證複製檔案的完整性,備份原始檔案最好是停止mysql伺服器,複製原始檔案備份由以下步驟完成:

  1. 停止mysql伺服器
    $ mysqladmin shutdown
  2. 使用合適的工具複製原始資料檔案
    $ tar cf /tmp/dbbackup.tar ./data
  3. 備份完成後,執行mysql伺服器
    $ mysqld_safe

使用主從備份模式

使用mysqldumptar備份或多或少都會對業務產生影響,使用mysqldump備份需要對資料加鎖,加鎖就意味著其他客戶端操作受到限制。使用tar命令需要停止伺服器直接導致資料庫伺服器不可用,有沒有辦法能解決這兩種問題呢?答案是有的,就是使用主從備份模式。

在單機的基礎上增加一臺Slave機器對Master機器的資料進行同步:

知識分享--資料庫

開始備份時對Slave進行備份,這樣即使Slave停機或對資料加鎖也不會影響業務的正常使用,如果公司有條件或業務非常重要可以選擇這種方案來備份資料。

MySQL常見優化方式

MySQL執行流程

知識分享--資料庫

1. 優化思路

1.1 優化什麼

在資料庫優化上有兩個主要方面:即安全與效能。 安全 —> 資料可持續性 效能 —> 資料的高效能訪問

1.2 優化的範圍有哪些

存 儲、主機和作業系統方面:

主機架構穩定性 I/O規劃及配置 Swap交換分割槽 OS核心引數和網路問題

應用程式方面:

應用程式穩定性 SQL語句效能 序列訪問資源 效能欠佳會話管理 這個應用適不適合用MySQL

資料庫優化方面

記憶體 資料庫結構(物理&邏輯) 例項配置

說明:不管是在,設計系統,定位問題還是優化,都可以按照這個順序執行。

1.3 優化維度

資料庫優化維度有四個:

硬體、系統配置、資料庫表結構、SQL及索引

知識分享--資料庫

2 優化工具有啥?

2.1 資料庫層面

檢查問題常用工具

mysql
msyqladmin                                 mysql客戶端,可進行管理操作
mysqlshow                                  功能強大的檢視shell命令
show [SESSION | GLOBAL] variables          檢視資料庫引數資訊
SHOW [SESSION | GLOBAL] STATUS             檢視資料庫的狀態資訊
information_schema                         獲取後設資料的方法
SHOW ENGINE INNODB STATUS                  Innodb引擎的所有狀態
SHOW PROCESSLIST                           檢視當前所有連線session狀態
explain                                    獲取查詢語句的執行計劃
show index                                 檢視錶的索引資訊
slow-log                                   記錄慢查詢語句
mysqldumpslow                              分析slowlog檔案的
複製程式碼

不常用但好用的工具

zabbix                  監控主機、系統、資料庫(部署zabbix監控平臺)
pt-query-digest         分析慢日誌
mysqlslap               分析慢日誌
sysbench                壓力測試工具
mysql profiling         統計資料庫整體狀態工具    
Performance Schema      mysql效能狀態統計的資料
workbench               管理、備份、監控、分析、優化工具(比較費資源)

2.2 資料庫層面問題解決思路

一般應急調優的思路:

針對突然的業務辦理卡頓,無法進行正常的業務處理!需要立馬解決的場景!

  1. show processlist
  2. explain select id ,name from stu where name=’clsn’; # ALL id name age sex
     select id,name from stu  where id=2-1 函式 結果集>30;
     show index from table;
  3. 通過執行計劃判斷,索引問題(有沒有、合不合理)或者語句本身問題
  4. show status like ‘%lock%’; # 查詢鎖狀態
      kill SESSION_ID; # 殺掉有問題的session

常規調優思路:
針對業務週期性的卡頓,例如在每天10-11點業務特別慢,但是還能夠使用,過了這段時間就好了。

1、檢視slowlog,分析slowlog,分析出查詢慢的語句。 2、按照一定優先順序,進行一個一個的排查所有慢語句。 3、分析top sql,進行explain除錯,檢視語句執行時間。 4、調整索引或語句本身。

2.3 系統層面

cpu方面

vmstat、sar top、htop、nmon、mpstat

記憶體

free 、ps -aux 、

IO裝置(磁碟、網路)

iostat 、 ss 、 netstat 、 iptraf、iftop、lsof、

vmstat 命令說明:

Procs:r顯示有多少程式正在等待CPU時間。b顯示處於不可中斷的休眠的程式數量。在等待I/O Memory:swpd顯示被交換到磁碟的資料塊的數量。未被使用的資料塊,使用者緩衝資料塊,用於作業系統的資料塊的數量 Swap:作業系統每秒從磁碟上交換到記憶體和從記憶體交換到磁碟的資料塊的數量。s1和s0最好是0 Io:每秒從裝置中讀入b1的寫入到裝置b0的資料塊的數量。反映了磁碟I/O System:顯示了每秒發生中斷的數量(in)和上下文交換(cs)的數量 Cpu:顯示用於執行使用者程式碼,系統程式碼,空閒,等待I/O的CPU時間

iostat命令說明

例項命令: iostat -dk 1 5      iostat -d -k -x 5 (檢視裝置使用率(%util)和響應時間(await)) tps:該裝置每秒的傳輸次數。“一次傳輸”意思是“一次I/O請求”。多個邏輯請求可能會被合併為“一次I/O請求”。 iops :硬體出廠的時候,廠家定義的一個每秒最大的IO次數 “一次傳輸”請求的大小是未知的。 kB_read/s:每秒從裝置(drive expressed)讀取的資料量; KB_wrtn/s:每秒向裝置(drive expressed)寫入的資料量; kB_read:讀取的總資料量; kB_wrtn:寫入的總數量資料量;這些單位都為Kilobytes。

2.4 系統層面問題解決辦法

在實際的生產中,一般認為 cpu只要不超過90%都沒什麼問題 。

當然不排除下面這些特殊情況:

問題一:cpu負載高,IO負載低

記憶體不夠 磁碟效能差 SQL問題 ——>去資料庫層,進一步排查sql問題 IO出問題了(磁碟到臨界了、raid設計不好、raid降級、鎖、在單位時間內tps過高) tps過高: 大量的小資料IO、大量的全表掃描

問題二:IO負載高,cpu負載低

大量小的IO 寫操作:

  autocommit ,產生大量小IO

  IO/PS,磁碟的一個定值,硬體出廠的時候,廠家定義的一個每秒最大的IO次數。

大量大的IO 寫操作

  SQL問題的機率比較大

問題三:IO和cpu負載都很高

硬體不夠了或sql存在問題

3 基礎優化

3.1 優化思路

定位問題點吮吸 硬體 –> 系統 –> 應用 –> 資料庫 –> 架構(高可用、讀寫分離、分庫分表)

處理方向 明確優化目標、效能和安全的折中、防患未然

3.2 硬體優化

主機方面:

根據資料庫型別,主機CPU選擇、記憶體容量選擇、磁碟選擇 平衡記憶體和磁碟資源 隨機的I/O和順序的I/O 主機 RAID卡的BBU(Battery Backup Unit)關閉

cpu的選擇:

cpu的兩個關鍵因素:核數、主頻 根據不同的業務型別進行選擇: cpu密集型:計算比較多,OLTP 主頻很高的cpu、核數還要多 IO密集型:查詢比較,OLAP 核數要多,主頻不一定高的

記憶體的選擇:

OLAP型別資料庫,需要更多記憶體,和資料獲取量級有關。 OLTP型別資料一般記憶體是cpu核心數量的2倍到4倍,沒有最佳實踐。

儲存方面:

根據儲存資料種類的不同,選擇不同的儲存裝置 配置合理的RAID級別(raid5、raid10、熱備盤)

對與作業系統來講,不需要太特殊的選擇,最好做好冗餘(raid1)(ssd、sas 、sata)

raid卡:主機raid卡選擇:

實現作業系統磁碟的冗餘(raid1)

平衡記憶體和磁碟資源

隨機的I/O和順序的I/O

主機 RAID卡的BBU(Battery Backup Unit)要關閉。

網路裝置方面:

使用流量支援更高的網路裝置(交換機、路由器、網線、網路卡、HBA卡)

注意:以上這些規劃應該在初始設計系統時就應該考慮好。

3.3 伺服器硬體優化

  1. 物理狀態燈:
  2. 自帶管理裝置:遠端控制卡(FENCE裝置:ipmi ilo idarc),開關機、硬體監控。
  3. 第三方的監控軟體、裝置(snmp、agent)對物理設施進行監控
  4. 儲存裝置:自帶的監控平臺。EMC2(hp收購了), 日立(hds),IBM低端OEM hds,高階儲存是自己技術,華為儲存

3.4 系統優化

Cpu:

基本不需要調整,在硬體選擇方面下功夫即可。

記憶體:

基本不需要調整,在硬體選擇方面下功夫即可。

SWAP:

MySQL儘量避免使用swap。 阿里雲的伺服器中預設swap為0

IO :

raid、no lvm、 ext4或xfs、ssd、IO排程策略

Swap調整(不使用swap分割槽)

/proc/sys/vm/swappiness的內容改成0(臨時),/etc/sysctl.conf上新增vm.swappiness=0(永久)

這個引數決定了Linux是傾向於使用swap,還是傾向於釋放檔案系統cache。在記憶體緊張的情況下,數值越低越傾向於釋放檔案系統cache。

當然,這個引數只能減少使用swap的概率,並不能避免Linux使用swap。

修改MySQL的配置引數innodb_flush_method,開啟O_DIRECT模式。

這種情況下,InnoDB的buffer pool會直接繞過檔案系統cache來訪問磁碟,但是redo log依舊會使用檔案系統cache。

值得注意的是,Redo log是覆寫模式的,即使使用了檔案系統的cache,也不會佔用太多

IO排程策略

#echo deadline>/sys/block/sda/queue/scheduler 臨時修改為deadline 永久修改

vi /boot/grub/grub.conf 更改到如下內容: kernel /boot/vmlinuz-2.6.18-8.el5 ro root=LABEL=/ elevator=deadline rhgb quiet

3.5 系統引數調整

Linux系統核心引數優化

vim /etc/sysctl.conf net.ipv4.ip_local_port_range = 1024 65535 # 使用者埠範圍 net.ipv4.tcp_max_syn_backlog = 4096 net.ipv4.tcp_fin_timeout = 30 fs.file-max=65535 # 系統最大檔案控制程式碼,控制的是能開啟檔案最大數量

使用者限制引數(mysql可以不設定以下配置)

vim /etc/security/limits.conf * soft nproc 65535 * hard nproc 65535 * soft nofile 65535 * hard nofile 65535

3.6 應用優化

業務應用和資料庫應用獨立,

防火牆:iptables、selinux等其他無用服務(關閉):

    chkconfig --level 23456 acpid off
    chkconfig --level 23456 anacron off
    chkconfig --level 23456 autofs off
    chkconfig --level 23456 avahi-daemon off
    chkconfig --level 23456 bluetooth off
    chkconfig --level 23456 cups off
    chkconfig --level 23456 firstboot off
    chkconfig --level 23456 haldaemon off
    chkconfig --level 23456 hplip off
    chkconfig --level 23456 ip6tables off
    chkconfig --level 23456 iptables  off
    chkconfig --level 23456 isdn off
    chkconfig --level 23456 pcscd off
    chkconfig --level 23456 sendmail  off
    chkconfig --level 23456 yum-updatesd  off

安裝圖形介面的伺服器不要啟動圖形介面 runlevel 3

另外,思考將來我們的業務是否真的需要MySQL,還是使用其他種類的資料庫。用資料庫的最高境界就是不用資料庫。

4 資料庫優化

SQL優化方向: 執行計劃、索引、SQL改寫

架構優化方向: 高可用架構、高效能架構、分庫分表

4.1 資料庫引數優化

調整:

例項整體(高階優化,擴充套件):

    thread_concurrency       # 併發執行緒數量個數
    sort_buffer_size         # 排序快取
    read_buffer_size         # 順序讀取快取
    read_rnd_buffer_size     # 隨機讀取快取
    key_buffer_size          # 索引快取
    thread_cache_size        # (1G—>8, 2G—>16, 3G—>32, >3G—>64)

連線層(基礎優化)

設定合理的連線客戶和連線方式

    max_connections           # 最大連線數,看交易筆數設定    
    max_connect_errors        # 最大錯誤連線數,能大則大
    connect_timeout           # 連線超時
    max_user_connections      # 最大使用者連線數
    skip-name-resolve         # 跳過域名解析
    wait_timeout              # 等待超時
    back_log                  # 可以在堆疊中的連線數量

4.2 儲存引擎層(innodb基礎優化引數)

default-storage-engine
innodb_buffer_pool_size       # 沒有固定大小,50%測試值,看看情況再微調。但是儘量設定不要超過實體記憶體70%
innodb_file_per_table=(1,0)
innodb_flush_log_at_trx_commit=(0,1,2) # 1是最安全的,0是效能最高,2折中
binlog_sync
Innodb_flush_method=(O_DIRECT, fdatasync)
innodb_log_buffer_size        # 100M以下
innodb_log_file_size          # 100M 以下
innodb_log_files_in_group     # 5個成員以下,一般2-3個夠用(iblogfile0-N)
innodb_max_dirty_pages_pct   # 達到百分之75的時候刷寫 記憶體髒頁到磁碟。
log_bin
max_binlog_cache_size         # 可以不設定
max_binlog_size               # 可以不設定
innodb_additional_mem_pool_size    #小於2G記憶體的機器,推薦值是20M。32G記憶體以上100M

4.3 SQL層(基礎優化)

一、EXPLAIN

做MySQL優化,我們要善用 EXPLAIN 檢視SQL執行計劃。

下面來個簡單的示例,標註(1,2,3,4,5)我們要重點關注的資料

知識分享--資料庫

  • type列,連線型別。一個好的sql語句至少要達到range級別。杜絕出現all級別
  • key列,使用到的索引名。如果沒有選擇索引,值是NULL。可以採取強制索引方式
  • key_len列,索引長度
  • rows列,掃描行數。該值是個預估值
  • extra列,詳細說明。注意常見的不太友好的值有:Using filesort, Using temporary

二、SQL語句中IN包含的值不應過多

MySQL對於IN做了相應的優化,即將IN中的常量全部儲存在一個陣列裡面,而且這個陣列是排好序的。但是如果數值較多,產生的消耗也是比較大的。再例如:select id from table_name where num in(1,2,3) 對於連續的數值,能用 between 就不要用 in 了;再或者使用連線來替換。

三、SELECT語句務必指明欄位名稱

SELECT *增加很多不必要的消耗(cpu、io、記憶體、網路頻寬);增加了使用覆蓋索引的可能性;當表結構發生改變時,前斷也需要更新。所以要求直接在select後面接上欄位名。

四、當只需要一條資料的時候,使用limit 1

這是為了使EXPLAIN中type列達到const型別

五、如果排序欄位沒有用到索引,就儘量少排序

六、如果限制條件中其他欄位沒有索引,儘量少用or

or兩邊的欄位中,如果有一個不是索引欄位,而其他條件也不是索引欄位,會造成該查詢不走索引的情況。很多時候使用 union all 或者是union(必要的時候)的方式來代替“or”會得到更好的效果

七、儘量用union all代替union

unionunion all的差異主要是前者需要將結果集合並後再進行唯一性過濾操作,這就會涉及到排序,增加大量的CPU運算,加大資源消耗及延遲。當然,union all的前提條件是兩個結果集沒有重複資料。

八、不使用ORDER BY RAND()

1.  select id from `table_name`
2.  order by rand() limit 1000;

上面的sql語句,可優化為

1.  select id from `table_name` t1 join
2.  (select rand() * (select max(id) from `table_name`) as nid) t2
3.  on t1.id > t2.nid limit 1000;

九、區分in和exists, not in和not exists

select * fromA where id in (select id fromB)

上面sql語句相當於

1.  select * fromA where exists
2.  (select * fromB where 表B.id=A.id)

區分in和exists主要是造成了驅動順序的改變(這是效能變化的關鍵),如果是exists,那麼以外層表為驅動表,先被訪問,如果是IN,那麼先執行子查詢。所以IN適合於外表大而內表小的情況;EXISTS適合於外表小而內表大的情況。

關於not in和not exists,推薦使用not exists,不僅僅是效率問題,not in可能存在邏輯問題。
原sql語句

1.  select colname … from A2.  where a.id not in (select b.id from B)

高效的sql語句

1.  select colname … from A表 Left join B表 on
2.  where a.id = b.id where b.id is null

取出的結果集如下圖表示,A表不在B表中的資料

知識分享--資料庫

十、使用合理的分頁方式以提高分頁的效率

select id,name from table_name limit 866613, 20

使用上述sql語句做分頁的時候,可能有人會發現,隨著表資料量的增加,直接使用limit分頁查詢會越來越慢。

優化的方法如下:可以取前一頁的最大行數的id,然後根據這個最大的id來限制下一頁的起點。比如此列中,上一頁最大的id是866612。sql可以採用如下的寫法:

select id,name from table_name where id> 866612 limit 20

十一、分段查詢

在一些使用者選擇頁面中,可能一些使用者選擇的時間範圍過大,造成查詢緩慢。主要的原因是掃描行數過多。這個時候可以通過程式,分段進行查詢,迴圈遍歷,將結果合併處理進行展示。

如下圖這個sql語句,掃描的行數成百萬級以上的時候就可以使用分段查詢

知識分享--資料庫

十二、避免在 where 子句中對欄位進行 null 值判斷

對於null的判斷會導致引擎放棄使用索引而進行全表掃描。

十三、不建議使用%字首模糊查詢

例如LIKE “%name”或者LIKE “%name%”,這種查詢會導致索引失效而進行全表掃描。但是可以使用LIKE “name%”

那如何查詢%name%

如下圖所示,雖然給secret欄位新增了索引,但在explain結果果並沒有使用

知識分享--資料庫

那麼如何解決這個問題呢,答案:使用全文索引

在我們查詢中經常會用到select id,fnum,fdst from table_name where user_name like '%zhangsan%';。這樣的語句,普通索引是無法滿足查詢需求的。慶幸的是在MySQL中,有全文索引來幫助我們。

建立全文索引的sql語法是:

ALTER TABLE `table_name` ADD FULLTEXT INDEX `idx_user_name` (`user_name`);

使用全文索引的sql語句是:

select id,fnum,fdst from table_name 

where match(user_name) against('zhangsan' in boolean mode);

注意:在需要建立全文索引之前,請聯絡DBA確定能否建立。同時需要注意的是查詢語句的寫法與普通索引的區別

十四、避免在where子句中對欄位進行表示式操作

比如

select user_id,user_project from table_name where age*2=36;

中對欄位就行了算術運算,這會造成引擎放棄使用索引,建議改成

select user_id,user_project from table_name where age=36/2;

十五、避免隱式型別轉換

where 子句中出現 column 欄位的型別和傳入的引數型別不一致的時候發生的型別轉換,建議先確定where中的引數型別

知識分享--資料庫

十六、對於聯合索引來說,要遵守最左字首法則

舉列來說索引含有欄位id,name,school,可以直接用id欄位,也可以id,name這樣的順序,但是nameschool都無法使用這個索引。所以在建立聯合索引的時候一定要注意索引欄位順序,常用的查詢欄位放在最前面

十七、必要時可以使用force index來強制查詢走某個索引

有的時候MySQL優化器採取它認為合適的索引來檢索sql語句,但是可能它所採用的索引並不是我們想要的。這時就可以採用force index來強制優化器使用我們制定的索引。

十八、注意範圍查詢語句

對於聯合索引來說,如果存在範圍查詢,比如between,>,<等條件時,會造成後面的索引欄位失效。

十九、關於JOIN優化

知識分享--資料庫

  • LEFT JOIN A表為驅動表
  • INNER JOIN MySQL會自動找出那個資料少的表作用驅動表
  • RIGHT JOIN B表為驅動表

注意:MySQL中沒有full join,可以用以下方式來解決

 select * from A left join B on B.name = A.name where B.name is null union all select * from B;

儘量使用inner join,避免left join

參與聯合查詢的表至少為2張表,一般都存在大小之分。如果連線方式是inner join,在沒有其他過濾條件的情況下MySQL會自動選擇小表作為驅動表,但是left join在驅動表的選擇上遵循的是左邊驅動右邊的原則,即left join左邊的表名為驅動表。

  • 合理利用索引
  • 被驅動表的索引欄位作為on的限制欄位。
  • 利用小表去驅動大表

知識分享--資料庫

從原理圖能夠直觀的看出如果能夠減少驅動表的話,減少巢狀迴圈中的迴圈次數,以減少 IO總量及CPU運算的次數。

巧用STRAIGHT_JOIN

inner join是由mysql選擇驅動表,但是有些特殊情況需要選擇另個表作為驅動表,比如有group byorder by「Using filesort」「Using temporary」時。STRAIGHT_JOIN來強制連線順序,在STRAIGHT_JOIN左邊的表名就是驅動表,右邊則是被驅動表。在使用STRAIGHT_JOIN有個前提條件是該查詢是內連線,也就是inner join。其他連結不推薦使用STRAIGHT_JOIN,否則可能造成查詢結果不準確。

知識分享--資料庫

MySQL的鎖

樂觀鎖與悲觀鎖

  • 樂觀鎖:每次讀資料的時候都認為其他人不會修改,所以不會上鎖,而是在更新的時候去判斷在此期間有沒有其他人更新了資料,可以使用版本號機制。在資料庫中可以通過為資料表增加一個版本號欄位實現。讀取資料時將版本號一同讀出,資料每次更新時對版本號加一。當我們更新的時候,判斷資料庫表對應記錄的當前版本號與第一次取出來的版本號值進行比對,如果值相等,則予以更新,否則認為是過期資料。樂觀鎖適用於多讀的應用型別,可以提高吞吐量。
  • 悲觀鎖:每次讀資料的時候都認為別人會修改,所以每次在讀資料的時候都會上鎖,這樣別人想讀這個資料時就會被阻塞。MySQL中就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在操作之前先上鎖。

共享鎖與排他鎖

  • 共享鎖:共享鎖又叫做讀鎖或S鎖,加上共享鎖後在事務結束之前其他事務只能再加共享鎖、只能對其進行讀操作不能寫操作,除此之外其他任何型別的鎖都不能再加了。
    # 加上lock in share mode
    SELECT description FROM book_book lock in share mode;
  • 排他鎖:排他鎖又叫寫鎖或X鎖,某個事務對資料加上排他鎖後,只能這個事務對其進行讀寫,在此事務結束之前,其他事務不能對其加任何鎖,可以讀取,不能進行寫操作,需等待其釋放。
    # 加上for update
    SELECT description FROM book_book for update; 

行鎖與表鎖

行鎖與表鎖區別在於鎖的粒度,在Innodb引擎中既支援行鎖也支援表鎖(MyISAM引擎只支援表鎖),只有通過索引條件檢索資料InnoDB才使用行級鎖,否則,InnoDB將使用表鎖。

  • 表鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突概率高,併發度最低
  • 行鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度小,發生鎖衝突的概率低,併發度高

這裡有個比較疑惑的地方,為什麼表鎖不會出現死鎖?在MyISAM中由於沒有事務,一條SQL執行完鎖就釋放了,不會迴圈等待,所以只會出現阻塞而不會發生死鎖。但是在**InnoDB中有事務就比較疑惑了,希望有了解的小夥伴指點指點@-@

舉例

# 事務1
BEGIN;
SELECT description FROM book_book where name = 'JAVA程式設計思想' lock in share mode;
# 事務2
BEGIN;
UPDATE book_book SET name = 'new book' WHERE name = 'new';
# 檢視事務狀態
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
trx_id  trx_state       trx_started           trx_tables_locked    trx_rows_locked
39452    LOCK WAIT    2018-09-08 19:01:39        1                    1    
282907511143936    RUNNING    2018-09-08 18:58:47        1                    38    

事務1給book表加上了共享鎖,事務2嘗試修改book表發生了阻塞,檢視事務狀態可以知道事務一由於沒有走索引使用了表鎖。

# 事務1
BEGIN;
SELECT description FROM book_book WHERE id = 2 lock in share mode;
# 事務2
BEGIN;
UPDATE book_book SET name = 'new book' WHERE id = 1; 
# 檢視事務狀態
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
trx_id          trx_state   trx_started     trx_tables_locked    trx_rows_locked
39454            RUNNING    2018-09-08 19:10:44    1                    1    
282907511143936    RUNNING    2018-09-08 19:10:35    1                    1

事務1給book表加上了共享鎖,事務2嘗試修改book表並沒有發生阻塞。這是由於事務一和事務二都走了索引,所以使用的是行鎖,並不會發生阻塞。

意向鎖(InnoDB特有)

意向鎖的意義在於方便檢測表鎖和行鎖之間的衝突

  • 意向鎖:意向鎖是一種表級鎖,代表要對某行記錄進行操作。分為意向共享鎖(IS)和意向排他鎖(IX)。

  • 行鎖和表鎖之間的衝突:事務A給表中的某一行加了共享鎖,讓這一行只能讀不能寫。之後事務B申請整個表的排他鎖。如果事務B申請成功,那麼它就能修改表中的任意一行,這與A持有的行鎖是衝突的。InnoDB引入了意向鎖來判斷它們之間的衝突。

  • 沒有意向鎖的情況:1、判斷表是否已被其他事務用表鎖鎖表。2、判斷表中的每一行是否已被行鎖鎖住,這樣要遍歷整個表,效率很低。

  • 意向鎖存在的情況:1、判斷表是否已被其他事務用表鎖鎖表。2、判斷表上是否有意向鎖

  • 意向鎖存在時申請鎖:*申請意向鎖的動作是資料庫完成的*,上述例子中事務A申請一行的行鎖的時候,資料庫會自動先開始申請表的意向鎖,當事務B申請表的排他鎖時檢測到存在意向鎖則會阻塞。

  • 意向鎖會不會存在衝突: 意向鎖之間不會衝突, 因為意向鎖只是代表要對某行記錄進行操作。

各種鎖之間的共存情況

IX IS X S
IX 相容 相容 衝突 衝突
IS 相容 相容 衝突 相容
X 衝突 衝突 衝突 衝突
S 衝突 相容 衝突 相容

死鎖

  • 概念:兩個或兩個以上的事務在執行過程中,因爭奪資源而造成的一種互相等待的現象。
  • 存在條件:1、 互斥條件:一個資源每次只能被一個事務使用。2、 請求與保持條件:一個事務因請求資源而阻塞時,對已獲得的資源保持不放。3、不剝奪條件:已獲得的資源,在末使用完之前不能強行剝奪。4、迴圈等待條件:形成一種頭尾相接的迴圈等待關係
  • 解除正在死鎖的狀態:撤銷其中一個事務

MVCC(多版本併發控制)

MVCC使得InnoDB更好的實現事務隔離級別中的REPEATABLE READ

  • 它使得InnoDB不再單純的使用行鎖來進行資料庫的併發控制,取而代之的是把資料庫的行鎖與行的多個版本結合起來,只需要很小的開銷,就可以實現非鎖定讀,從而大大提高資料庫系統的併發效能。

  • 實現:InnoDB實現MVCC的方法是它為每一行儲存三個額外的隱藏欄位

  • 1.DB_TRX_ID:一個6byte的標識,每處理一個事務,其值自動+1 ,可以通過語句“show engine innodb status”來查詢

  • 2.DB_ROLL_PTR: 大小是7byte,指向寫到rollback segment(回滾段)的一條undo log記錄

  • 3.DB_ROW_ID: 大小是6byte,該值隨新行插入單調增加。

  • SELECT:返回的行資料需要滿足的條件: 1、資料行的建立版本號必須小於等於事務的版本2、行的刪除版本號(行中的特殊位被設定為將其標記為已刪除)一定是未定義的或者大於當前事務的版本號,確定了當前事務開始之前行沒有被刪除。

  • INSERT:InnoDB為每個新增行記錄當前系統版本號作為建立版本號。

  • DELETE:InnoDB為每個刪除行的記錄當前系統版本號作為行的刪除版本號。

  • UPDATE:InnoDB複製了一條資料。這條資料的版本號使用了系統版本號。它也把系統版本號作為老資料的刪除號。

  • 說明:這裡的讀是不加鎖的select等,MVCC實現可重複讀使用的是讀取undo中的已經提交的資料,是非阻塞的。insert操作時”建立時間”=DB_ROW_ID,這時”刪除時間”是未定義的;update時,複製新增行的”建立時間”=DB_ROW_ID,刪除時間未定義,舊資料行”建立時間”不變,刪除時間=該事務的DB_ROW_ID; delete操作,相應資料行的”建立時間”不變,刪除時間=該事務的DB_ROW_ID;

間隙鎖(Next-Key鎖)

間隙鎖使得InnoDB解決幻讀問題,加上MVCC使得InnoDB的RR隔離級別實現了序列化級別的效果,並且保留了比較好的併發效能。

定義:當我們用範圍條件檢索資料時請求共享或排他鎖時,InnoDB會給符合條件的已有資料的索引加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做間隙(GAP),InnoDB也會對這個”間隙”加鎖,這種鎖機制就是間隙鎖。

例如:book表中存在bookId 1-80,90-99的記錄。SELECT * FROM book WHERE bookId < 100 FOR UPDATE。InnoDB不僅會對bookId值為1-80,90-99的記錄加鎖,也會對bookId在81-89之間(這些記錄並不存在)的間隙加鎖。這樣就能避免事務隔離級別可重複讀下的幻讀。

badger

Level DB

Redis

資料結構

  • String
  • List
  • Set
  • Hash
  • SortedSet
  • Hyperloglog
  • Geo
  • PubSub

模組

  • RedisSearch
  • BloomFilter
  • Redis-ML

Redis分散式鎖

分散式鎖的實現方式

  1. 資料庫的樂觀鎖
  2. 給予zookeeper的分散式鎖
  3. 給予redis的分散式鎖

分散式鎖的注意事項

  1. 互斥性:在任意時刻,只有一個客戶端能持有鎖

  2. 同一性:加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。

  3. 避免死鎖:即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其他客戶端能加鎖。

    Redis實現方式

  • setnx+del+expire用法
  • Redis 2.6.12以上版本為set指令增加了可選引數,虛擬碼如下:set(key,1,30,NX),這樣就可以取代setnx指令。
  • 單條指令合併setnx 和 expire, lua指令碼瞭解一下

Redis實現事務

  • Redis的事務是通過MULTI,EXEC,DISCARD和WATCH這四個命令來完成。
  • Redis的單個命令都是原子性的,所以這裡確保事務性的物件是命令集合
  • Redis將命令集合序列化並確保處於一事務的命令集合連續且不被打斷的執行。
  • Redis不支援回滾的操作。
  • MULTI
        注:用於標記事務塊的開始
        Redis會將後續的命令逐個放入佇列中,然後使用EXEC命令原子化地執行這個命令序列。
        語法:MULTI
  • EXEC
        在一個事務中執行所有先前放入佇列的命令,然後恢復正常的連線狀態。
        語法:EXEC
  • DISCARD
        清楚所有先前在一個事務中放入佇列的命令,然後恢復正常的連線狀態。
        語法:DISCARD
  • WATCH
        當某個事務需要按條件執行時,就要使用這個命令將給定的鍵設定為受監控狀態
        語法:WATCH key [key ….]
        注:該命令可以實現redis的樂觀鎖
  • UNWATCH
        清除所有先前為一個事務監控的鍵。
        語法:UNWATCH

為什麼redis不支援事務回滾?

  1. 大多數事務失敗是因為語法錯誤或者型別錯誤,這兩種錯誤,再開發階段都是可以避免的
  2. Redis為了效能方面就忽略了事務回滾

掃描Key

  • keys (有問題,慎用,最好不用)
  • scan

Redis做非同步佇列

  • 使用lis- t結構作為佇列
  • rpush生產訊息,lpop消費訊息, lpop沒有訊息的時候,要適當sleep一會再重試
  • blpop,在沒有訊息的時候,它會阻塞住直到訊息到來
  • 生產一次消費多次: 使用pub/sub主題訂閱者模式,可以實現1:N的訊息佇列
  • pub/sub有什麼缺點?在消費者下線的情況下,生產的訊息會丟失,得使用專業的訊息佇列如rabbitmq等
  • redis如何實現延時佇列 – 使用sortedset,拿時間戳作為score,訊息內容作為key呼叫zadd來生產訊息,消費者用zrangebyscore指令獲取N秒之前的資料輪詢進行處理

大量的key需要設定同一時間過期,一般需要注意什麼

過期的那個時間點,redis可能會出現短暫的卡頓現象。嚴重的話會出現快取雪崩,我們一般需要在時間上加一個隨機值,使得過期時間分散一些

Redis如何做持久化的

  • bgsave做映象全量持久化(RDB),aof做增量持久化
  • 為bgsave會耗費較長時間,不夠實時,在停機的時候會導致大量丟失資料,所以需要aof來配合使用。
  • 在redis例項重啟時,會使用bgsave持久化檔案重新構建記憶體,再使用aof重放近期的操作指令來實現完整恢復重啟之前的狀態
  • 如果突然機器掉電會怎樣?取決於aof日誌sync屬性的配置,如果不要求效能,在每條寫指令時都sync一下磁碟,就不會丟失資料。但是在高效能的要求下每次都sync是不現實的,一般都使用定時sync,比如1s1次,這個時候最多就會丟失1s的資料。
  • bgsave的原理是什麼 – fork和cow。fork是指redis通過建立子程式來進行bgsave操作,cow指的是copy on write,子程式建立後,父子程式共享資料段,父程式繼續提供讀寫服務,寫髒的頁面資料會逐漸和子程式分離開來。

RDB 優缺點

  • RDB會生成多個資料檔案,每個資料檔案都代表了某一個時刻中 redis 的資料,這種多個資料檔案的方式,非常適合做冷備,可以將這種完整的資料檔案傳送到一些遠端的安全儲存上去,比如說 Amazon 的 S3 雲服務上去,在國內可以是阿里雲的 ODPS 分散式儲存上,以預定好的備份策略來定期備份redis中的資料。

    RDB 對 redis 對外提供的讀寫服務,影響非常小,可以讓 redis 保持高效能,因為 redis 主程式只需要 fork 一個子程式,讓子程式執行磁碟 IO 操作來進行 RDB 持久化即可。

  • 相對於 AOF 持久化機制來說,直接基於 RDB 資料檔案來重啟和恢復 redis 程式,更加快速。
  • 如果想要在 redis 故障時,儘可能少的丟失資料,那麼 RDB 沒有 AOF 好。一般來說,RDB 資料快照檔案,都是每隔 5 分鐘,或者更長時間生成一次,這個時候就得接受一旦 redis 程式當機,那麼會丟失最近 5 分鐘的資料。

    RDB 每次在 fork 子程式來執行 RDB 快照資料檔案生成的時候,如果資料檔案特別大,可能會導致對客戶端提供的服務暫停數毫秒,或者甚至數秒。

RDB觸發條件

  1. 符合自定義配置的快照規則
  2. 執行save或者bgsave命令
  3. 執行flushall命令
  4. 執行主從複製操作

在redis.conf中設定自定義快照規則

1、RDB持久化條件

格式:save <seconds> <changes>

示例:

  • save 900 1:表示15分鐘(900秒)內至少1個鍵更改則進行快照。
  • save 300 10:表示5分鐘(300秒)內至少10個鍵被更改則進行快照。
  • save 60 10000:表示1分鐘內至少10000個鍵被更改則進行快照。

2、配置dir指定rdb快照檔案的位置

# Note that you must specify a directory here, not a file name.
dir ./

3、配置dbfilename指定rdb快照檔案的名稱

# The filename where to dump the DB
dbfilename dump.rdb

說明

  1. Redis啟動後會讀取RDB快照檔案,將資料從硬碟載入到記憶體
  2. 根據資料量大小與結構和伺服器效能不同,這個時間也不同。通常將記錄1千萬個字串型別鍵,大小為1GB的快照檔案載入到記憶體中需要花費20-30秒鐘。

快照的實現原理

  1. redis使用fork函式複製一份當前程式副本(子程式)
  2. 父程式繼續接受並處理客戶端發來的命令,而子程式開始將記憶體中的資料寫入到硬碟**臨時檔案**。
  3. 子程式寫入完所有資料後用該臨時檔案替換舊的RDB檔案,至此,一次快照操作完成。

注意

  1. redis在進行快照的過程中不會修改RDB檔案,只有快照結束後才會將舊的檔案替換成新的,也就是說任何時候RDB檔案都是完整的。
  2. 這就使得我們可以通過定時備份RDB檔案來實現redis資料庫的備份,RDB檔案是經過壓縮的二進位制檔案,佔用的空間會小於記憶體中的資料,更加利於傳輸。

AOF 優缺點

  • AOF 可以更好的保護資料不丟失,一般 AOF 會每隔 1 秒,通過一個後臺執行緒執行一次fsync操作,最多丟失 1 秒鐘的資料。
  • AOF 日誌檔案以 append-only 模式寫入,所以沒有任何磁碟定址的開銷,寫入效能非常高,而且檔案不容易破損,即使檔案尾部破損,也很容易修復。
  • AOF 日誌檔案即使過大的時候,出現後臺重寫操作,也不會影響客戶端的讀寫。因為在 rewrite log 的時候,會對其中的指導進行壓縮,建立出一份需要恢復資料的最小日誌出來。再建立新日誌檔案的時候,老的日誌檔案還是照常寫入。當新的 merge 後的日誌檔案 ready 的時候,再交換新老日誌檔案即可。
  • AOF 日誌檔案的命令通過非常可讀的方式進行記錄,這個特性非常適合做災難性的誤刪除的緊急恢復。比如某人不小心用 flushall 命令清空了所有資料,只要這個時候後臺 rewrite 還沒有發生,那麼就可以立即拷貝 AOF 檔案,將最後一條 flushall 命令給刪了,然後再將該 AOF 檔案放回去,就可以通過恢復機制,自動恢復所有資料。
  • 對於同一份資料來說,AOF 日誌檔案通常比 RDB 資料快照檔案更大。
  • AOF 開啟後,支援的寫 QPS 會比 RDB 支援的寫 QPS 低,因為 AOF 一般會配置成每秒 fsync 一次日誌檔案,當然,每秒一次 fsync,效能也還是很高的。(如果實時寫入,那麼 QPS 會大降,redis 效能會大大降低)
  • 以前 AOF 發生過 bug,就是通過 AOF 記錄的日誌,進行資料恢復的時候,沒有恢復一模一樣的資料出來。所以說,類似 AOF 這種較為複雜的基於命令日誌/merge/回放的方式,比基於 RDB 每次持久化一份完整的資料快照檔案的方式,更加脆弱一些,容易有 bug。不過 AOF 就是為了避免 rewrite 過程導致的 bug,因此每次 rewrite 並不是基於舊的指令日誌進行 merge 的,而是基於當時記憶體中的資料進行指令的重新構建,這樣健壯性會好很多。

AOF重寫原理(優化AOF檔案)

  1. Redis可以在AOF檔案體積變得過大時,自動地後臺對AOF進行重寫
  2. 重寫後的新AOF檔案包含了恢復當前資料集所需的最小命令集合
  3. 整個重寫操作是絕對安全的,因為Redis在建立新的AOF檔案的過程中,會繼續將命令追加到現有的AOF檔案裡面,即使重寫過程中發生停機,現有的AOF檔案也不會丟失。而一旦新AOF檔案建立完畢,Redis就會從舊AOF檔案切換到新AOF檔案,並開始對新AOF檔案進行追加操作。
  4. AOF檔案有序地儲存了對資料庫執行的所有寫入操作,這些寫入操作以Redis協議的格式儲存,因此AOF檔案的內容非常容易被人讀懂,對檔案進行分析(parse)也很輕鬆。

同步磁碟資料

Redis每次更改資料的時候,aof機制都會將命令記錄到aof檔案,但是實際上由於作業系統的快取機制資料實時寫入到硬碟,而是進入硬碟快取再通過硬碟快取機制去重新整理到儲存檔案中

引數說明#

  1. appendfsync always:每次執行寫入都會進行同步,這個是最安全但是效率比較低
  2. appendfsync everysec:每一秒執行
  3. appendfsync no:不主動進行同步操作,由於作業系統去執行,這個是最快但是最不安全的方式

AOF檔案損壞以後如何修復

伺服器可能在程式正在對AOF檔案進行寫入時停機,如果停機造成AOF檔案出錯(corrupt),那麼Redis在重啟時會拒絕載入這個AOF檔案,從而確保資料的一致性不會被破壞。

當發生這種情況時,可以以以下方式來修復出錯的AOF檔案:

  • 為現有的AOF檔案建立一個備份。
  • 使用Redis附帶的redis-check-aof程式,對原來的AOF檔案進行修復。
  • 重啟Redis伺服器,等待伺服器字啊如修復後的AOF檔案,並進行資料恢復。

如何選擇RDB和AOF

  1. 一般來說,如果對資料的安全性要求非常高的話,應該同時使用兩種持久化功能。
  2. 如果可以承受數分鐘以內的資料丟失,那麼可以只使用RDB持久化。
  3. 有很多使用者都只使用AOF持久化,但並不推薦這種方式:因為定時生成RDB快照(snapshot)非常便於進行資料庫備份,並且RDB恢復資料集的速度也要比AOF恢復的速度要快
  4. 兩種持久化策略可以同時使用,也可以使用其中一種。如果同時使用的話,那麼Redis啟動時,會優先使用AOF檔案來還原資料。

Redis的同步機制

Redis可以使用主從同步,從從同步。第一次同步時,主節點做一次bgsave,並同時將後續修改操作記錄到記憶體buffer,待完成後將rdb檔案全量同步到複製節點,複製節點接受完成後將rdb映象載入到記憶體。載入完成後,再通知主節點將期間修改的操作記錄同步到複製節點進行重放就完成了同步過程

  • redis 採用非同步方式複製資料到 slave 節點,不過 redis2.8 開始,slave node 會週期性地確認自己每次複製的資料量;
  • 一個 master node 是可以配置多個 slave node 的;
  • slave node 也可以連線其他的 slave node;
  • slave node 做複製的時候,不會 block master node 的正常工作;
  • slave node 在做複製的時候,也不會 block 對自己的查詢操作,它會用舊的資料集來提供服務;但是複製完成的時候,需要刪除舊資料集,載入新資料集,這個時候就會暫停對外服務了;
  • slave node 主要用來進行橫向擴容,做讀寫分離,擴容的 slave node 可以提高讀的吞吐量。

注意,如果採用了主從架構,那麼建議必須開啟 master node 的持久化,不建議用 slave node 作為 master node 的資料熱備,因為那樣的話,如果你關掉 master 的持久化,可能在 master 當機重啟的時候資料是空的,然後可能一經過複製, slave node 的資料也丟了。

AOF檔案損壞以後如何修復

伺服器可能在程式正在對AOF檔案進行寫入時停機,如果停機造成AOF檔案出錯(corrupt),那麼Redis在重啟時會拒絕載入這個AOF檔案,從而確保資料的一致性不會被破壞。

當發生這種情況時,可以以以下方式來修復出錯的AOF檔案:

  • 為現有的AOF檔案建立一個備份。
  • 使用Redis附帶的redis-check-aof程式,對原來的AOF檔案進行修復。
  • 重啟Redis伺服器,等待伺服器字啊如修復後的AOF檔案,並進行資料恢復

redis 主從複製的核心原理

  • 當啟動一個 slave node 的時候,它會傳送一個 PSYNC 命令給 master node。
  • 如果這是 slave node 初次連線到 master node,那麼會觸發一次 full resynchronization 全量複製。此時 master 會啟動一個後臺執行緒,開始生成一份 RDB 快照檔案,同時還會將從客戶端 client 新收到的所有寫命令快取在記憶體中。RDB 檔案生成完畢後, master 會將這個 RDB 傳送給 slave,slave 會先寫入本地磁碟,然後再從本地磁碟載入到記憶體中,接著 master 會將記憶體中快取的寫命令傳送到 slave,slave 也會同步這些資料。
  • slave node 如果跟 master node 有網路故障,斷開了連線,會自動重連,連線之後 master node 僅會複製給 slave 部分缺少的資料。

過期 key 處理

slave 不會過期 key,只會等待 master 過期 key。如果 master 過期了一個 key,或者通過 LRU 淘汰了一個 key,那麼會模擬一條 del 命令傳送給 slave。

主從複製的斷點續傳

從 redis2.8 開始,就支援主從複製的斷點續傳,如果主從複製過程中,網路連線斷掉了,那麼可以接著上次複製的地方,繼續複製下去,而不是從頭開始複製一份。

master node 會在記憶體中維護一個 backlog,master 和 slave 都會儲存一個 replica offset 還有一個 master run id,offset 就是儲存在 backlog 中的。如果 master 和 slave 網路連線斷掉了,slave 會讓 master 從上次 replica offset 開始繼續複製,如果沒有找到對應的 offset,那麼就會執行一次 resynchronization

全量複製

master 執行 bgsave ,在本地生成一份 rdb 快照檔案。

master node 將 rdb 快照檔案傳送給 slave node,如果 rdb 複製時間超過 60秒(repl-timeout),那麼 slave node 就會認為複製失敗,可以適當調大這個引數(對於千兆網路卡的機器,一般每秒傳輸 100MB,6G 檔案,很可能超過 60s)

master node 在生成 rdb 時,會將所有新的寫命令快取在記憶體中,在 slave node 儲存了 rdb 之後,再將新的寫命令複製給 slave node。

如果在複製期間,記憶體緩衝區持續消耗超過 64MB,或者一次性超過 256MB,那麼停止複製,複製失敗。

client-output-buffer-limit slave 256MB 64MB 60

slave node 接收到 rdb 之後,清空自己的舊資料,然後重新載入 rdb 到自己的記憶體中,同時基於舊的資料版本對外提供服務。

如果 slave node 開啟了 AOF,那麼會立即執行 BGREWRITEAOF,重寫 AOF。

增量複製

如果全量複製過程中,master-slave 網路連線斷掉,那麼 slave 重新連線 master 時,會觸發增量複製。

master 直接從自己的 backlog 中獲取部分丟失的資料,傳送給 slave node,預設 backlog 就是1MB。

msater就是根據 slave 傳送的 psync 中的 offset 來從 backlog 中獲取資料的。

heartbeat

主從節點互相都會傳送 heartbeat 資訊。

master 預設每隔 10秒 傳送一次 heartbeat,slave node 每隔 1秒 傳送一個 heartbeat。

非同步複製

master 每次接收到寫命令之後,先在內部寫入資料,然後非同步傳送給 slave node。

Redis叢集,叢集的原理是什麼

Redis Sentinal著眼於高可用,在master當機時會自動將slave提升為master,繼續提供服務。

Redis Sentinel哨兵機制

Sentinel(哨兵)程式是用於監控redis叢集中Master主伺服器工作的狀態,在Master主伺服器發生故障的時候,可以實現Master和Slave伺服器的切換,保證系統的高可用,其已經被整合在redis2.6+的版本中,Redis的哨兵模式到2.8版本之後就穩定了下來。

哨兵程式的作用
  1. 監控(Monitoring):哨兵(Sentinel)會不斷地檢查你的Master和Slave是否運作正常。
  2. 提醒(Notification):當被監控的某個Redis節點出現問題時,哨兵(Sentinel)可以通過API向管理員或者其他應用程式傳送通知。
  3. 自動故障遷移(Automatic failover):當一個Master不能正常工作時,哨兵(Sentinel)會開始一次自動故障遷移操作。
    1. 它會將失效Master的其中一個Slave升級為新的Master,並讓失效Master的其他Slave改為複製新的Master;
    2. 當客戶端檢視連線失效的Master時,叢集也會向客戶端返回新Master的地址,使得叢集可以使用現在的Master替換失效的Master。
    3. Master和Slave伺服器切換後,Master的redis.conf、Slave的redis.conf和sentinel.conf的配置檔案的內容都會發生相應的改變,即Master主伺服器的redis.conf配置檔案中會多一行Slave的配置,sentinel.conf的監控目標會隨之調換。
哨兵程式的工作方式#
  1. 每個Sentinel(哨兵)程式以每秒鐘一次的頻率向整個叢集中的Master主伺服器Slave從伺服器以及其他Sentinel(哨兵)程式傳送一個PING命令
  2. 如果一個例項(instance)距離最後一次有效回覆PING命令的時間超過down-after-milliseconds選項所指定的值,則這個例項會被Sentinel(哨兵)程式標記為主觀下線(SDOWN)。
  3. 如果一個Master主伺服器被標記為主觀下線(SDOWN),則正在監視這個Master主伺服器的所有Sentinel(哨兵)程式要以每秒一次的頻率確認Master主伺服器確實進入主觀下線狀態
  4. 當有足夠數量的Sentinel(哨兵)程式(大於等於配置檔案指定的值)在指定的時間範圍內確認Master主伺服器進入了主觀下線狀態(SDOWN),則Master主伺服器會被標記為客觀下線(ODOWN)
  5. 在一般情況下,每個Sentinel(哨兵)程式會以每10秒一次的頻率向叢集中的所有Master主伺服器、Slave從伺服器傳送INFO命令。
  6. 當Master主伺服器被Sentinel(哨兵)程式標記為客觀下線(ODOWN)時,Sentinel(哨兵)程式向下線的Master主伺服器的所有Slave從伺服器傳送INFO命令的頻率會從10秒一次改為每秒一次。
  7. 若沒有足夠數量的Sentinel(哨兵)程式同意Master主伺服器下線,Master主伺服器的客觀下線狀態就會被移除。若Master主伺服器重新向Sentinel(哨兵)程式傳送PING命令返回有效回覆,Master主伺服器的主觀下線狀態就會被移除。

Redis Cluster

  • Redis叢集最少需要三臺主伺服器,三臺從伺服器
  • 先啟動伺服器例項,然後執行建立叢集命令列
    ./redis-trib.rb create --replicas 1 192.168.242.129:7001 192.168.242.129:7002 
      192.168.242.129:7003 192.168.242.129:7004 192.168.242.129:7005  192.168.242.129:7006
架構細節#
  1. 所有的redis節點彼此互聯(PING-PING機制),內部使用二進位制協議優化傳輸速度和頻寬。

  2. 節點的fail是通過叢集中超過半數的節點檢測失效時才生效。

  3. 客戶端與redis節點直連,不需要中間proxy層,客戶端不需要連線叢集所有節點,連線叢集中任何一個可用節點即可。

  4. redis-cluster把所有的物理節點對映到[0-16383]slot上,cluster負責維護node<->slot<->value

    Redis叢集中內建了16384個雜湊槽,當需要在Redis叢集中放置一個key-value時,redis先對key使用crc16演算法算出一個結果,然後把結果對16384求餘數,
    這樣每個key都會對應一個編號在0-16384之間的雜湊槽,redis會根據節點數量大致均等的將雜湊槽對映到不同節點。
    Redis Cluster著眼於擴充套件性,在單個redis記憶體不足時,使用Cluster進行分片儲存。

redis 如何才能做到高可用

如果系統在 365 天內,有 99.99% 的時間,都是可以嘩嘩對外提供服務的,那麼就說系統是高可用的。一個 slave 掛掉了,是不會影響可用性的,還有其它的 slave 在提供相同資料下的相同的對外的查詢服務。但是,如果 master node 死掉了,會怎麼樣?沒法寫資料了,寫快取的時候,全部失效了。slave node 還有什麼用呢,沒有 master 給它們複製資料了,系統相當於不可用了。redis 的高可用架構,叫做 failover 故障轉移,也可以叫做主備切換。master node 在故障時,自動檢測,並且將某個 slave node 自動切換位 master node的過程,叫做主備切換。這個過程,實現了 redis 的主從架構下的高可用。

Pipeline有什麼好處,為什麼要用pipeline?

可以將多次IO往返的時間縮減為一次,前提是pipeline執行的指令之間沒有因果相關性。使用redis-benchmark進行壓測的時候可以發現影響redis的QPS峰值的一個重要因素是pipeline批次指令的數目。

批處理

lua指令碼語言瞭解一下

Redis 的過期策略都有哪些?記憶體淘汰機制都有哪些?

redis 過期策略是:定期刪除+惰性刪除+記憶體淘汰機制 ****。

定期刪除,指的是 redis 預設是每隔 100ms 就隨機抽取一些設定了過期時間的 key,檢查其是否過期,如果過期就刪除。

惰性刪除。這就是說,在你獲取某個 key 的時候,redis 會檢查一下 ,這個 key 如果設定了過期時間那麼是否過期了?如果過期了此時就會刪除,不會給你返回任何東西。

記憶體淘汰機制
Redis 記憶體淘汰機制有以下幾個:

  • noeviction: 當記憶體不足以容納新寫入資料時,新寫入操作會報錯,這個一般沒人用吧,實在是太噁心了。
  • allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的 key(這個是最常用的)。
  • allkeys-random:當記憶體不足以容納新寫入資料時,在鍵空間中,隨機移除某個 key,這個一般沒人用吧,為啥要隨機,肯定是把最近最少使用的 key 給幹掉啊。
  • volatile-lru:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的 key(這個一般不太合適)。
  • volatile-random:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,隨機移除某個 key。
  • volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的 key 優先移除。

redis-cluster投票:容錯

  1. 叢集中所有master參與投票,如果半數以上master節點與其中一個master節點通訊超過(cluster-node-timeout),認為該master節點掛掉。
  2. 什麼時候整個叢集不可用(cluster_state:fail)?
    1. 如果叢集任意master掛掉,且當前master沒有slave,則叢集進入fail狀態。也可以理解成叢集的[0-16384]slot對映不完全時進入fail狀態。
    2. 如果叢集超過半數以上master掛掉,無論是否有slave,叢集進入fail狀態。

RocksDB

Dynamo–建設中

Greenplum–建設中

TDEngin–建設中

TiDB–建設中

Cassandra

HBase

  • 表:HBase採用表來組織資料,表由行和列組成,列劃分為若干列族。
  • 行:每個HBase表都由若干行組成,每個行由行鍵(row key)來標識。
  • 列族:一個HBase表被分組成許多“列族”(Column Family)的集合,它是基本的訪問控制單元。
  • 列限定符:列族裡的資料通過限定符(或列)來定位。
  • 單元格:在HBase表中,通過行、列族和列限定符確定一個“單元格”(cell),單元格中儲存的資料沒有資料型別,總被視為位元組陣列byte[]
  • 時間戳:每個單元格都儲存著同一份資料的多個版本,這些版本採用時間戳進行索引。

Hbase的實現原理

HBase的實現包括三個主要的功能元件:

  • 1、庫函式:連結到每個客戶端
  • 2、一個Master主伺服器
  • 3、許多個Region伺服器

主伺服器Master負責管理和維護Hbase表的分割槽資訊,維護Region伺服器列表,分配Region,負載均衡。

Region伺服器負責儲存和維護分配給自己的Region,處理來自客戶端的讀寫請求。

客戶端並不是直接從Master主伺服器上讀取資料,而是在獲得Region的儲存位置資訊後,直接從Region伺服器上讀取資料。

客戶端並不依賴Master,而是通過Zookeeper來Region位置資訊,大多數客戶端甚至從來不和Master通訊,這種設計方式使得Master負載很小。

知識分享--資料庫

客戶端

客戶端包含訪問Hbase的介面,同時在快取中維護著已經訪問過的Region位置資訊,用來加快後續資料訪問過程。

Zookeeper伺服器

Zookeeper可以幫助選舉出一個Master作為叢集的總管,並保證在任何時刻總有唯一一個Master在執行,這就避免了Master的“單點失效”的問題。

Master伺服器

主伺服器Master主要負責表和Region的管理工作:

  • 管理使用者對錶的增加、刪除、修改、查詢等操作
  • 實現不同Region伺服器之間的負載均衡
  • 在Region分裂或合併後,負責重新調整Region的分佈
  • 對發生故障失效的Region伺服器上Region進行遷移

Region伺服器

Region伺服器是Hbase中最核心的模組,負責維護分配給自己的Region,並響應使用者的讀寫請求

Region伺服器向HDFS檔案系統中讀寫資料過程:

  • 1、使用者讀寫資料過程
    • 使用者寫入資料時,被分配到相應Region伺服器去執行
    • 使用者資料首先被寫入到MEMStore和Hlog中
    • 只有當操作寫入Hlog之後,commit()呼叫才會將其返回給客戶端
    • 當使用者讀取資料時,Region伺服器首先訪問MEMStore快取,如果找不到,再去磁碟上面的StoreFile中尋找
  • 2、快取的重新整理
    • 系統會週期性地把MemStore快取裡的內容刷寫到磁碟的StoreFile檔案中,清空快取,並在Hlog裡面寫入一個標記
    • 每次刷寫都生成一個新的StoreFile檔案,因此,每個Store包含多個StoreFile檔案
    • 每個Region伺服器都有一個自己的HLog 檔案,每次啟動都檢查該檔案,確認最近一次執行快取重新整理操作之後是否發生新的寫入操作;如果發現更新,則先寫入MemStore,再刷寫到StoreFile,最後刪除舊的Hlog檔案,開始為使用者提供服務。
  • 3、StoreFile的合併
    • 每次刷寫都生成一個新的StoreFile,數量太多,影響查詢速度、
    • 呼叫Store.compact()把多個合併成一個
    • 合併操作比較耗費資源,只有數量達到一個閾值才啟動合併

在Hbase之上構建SQL引擎

NoSQL區別於關係型資料庫的一點就是NoSQL不使用SQL作為查詢語言,至於為何在NoSQL資料儲存HBase上提供SQL介面,有如下原因:

  • 1、易使用。使用諸如SQL這樣易於理解的語言,使人們能夠更加輕鬆地使用Hasee。
  • 2、減少編碼。使用諸如SQL這樣更高層次的語言來編寫,減少了編寫的程式碼量。

解決方案:Hive整合HBase

  • Hive與HBase的整合功能從Hive0.6.0版本已經開始出現,利用兩者對外的API介面互相通訊,通訊主要依靠hive_hbase-handler.jar工具包(Hive
  • Storage Handlers)。由於HBase有一次比較大的版本變動,所以並不是每個版本的Hive都能和現有的HBase版本進行整合,所以在使用過程中特別注意的就是兩者版本的一致性。

構建Hbase二級索引

HBase只有一個針對行鍵的索引,訪問Hbase表中的行,只有三種方式:

  • 通過單個行鍵訪問
  • 通過一個行鍵的區間來訪問
  • 全表掃描

使用其他產品為Hbase行鍵提供索引功能:

  • Hindex二級索引
  • Hbase+Redis
  • Hbase+solr

dgraph–建設中

JanusGraph–建設中

Neo4j–建設中

TigerGraph–建設中

Titan–建設中

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章