MySQL實戰-4

目錄

誤刪除的恢復方案

kill不掉的語句

大批量數據查詢

join原理

join優化


誤刪除的恢復方案

誤刪行
通過flashback恢復,但binlog需要設置成row模式
對於單個事務做如下處理

  1. 對於insert,對應的binlog event類型是Write_row_event,改成Delete類型
  2. 對於delete語句,改成Write類型
  3. 對於update_rows語句,binlog裏面記錄了修改前和修改後的值,對調這兩行的位置

對於多個事務

(A)delete ...
(B)insert ...
(C)update ...

恢復的時候按相反順序恢復

(reverse C)update ...
(reverse B)delete ...
(reverse A)insert ...

以上操作要在從庫上執行

誤刪表/庫
需要用全量備份+實時備份binlog來恢復
流程如下

  1. 取最近一次全量備份,假設一天一次上次備份就是當天0點
  2. 用備份恢復出一個臨時庫
  3. 從日誌備份裏面,取出凌晨0點之後的日誌
  4. 把這些日誌,除了誤刪除的語句外,全部應用到臨時庫

如果是一週備份一次,那恢復的時間會非常長
可以搞一個專門用於備份的從庫
設置 change master to master_delay=N,這個N的單位是秒,設置成3600就表示跟主庫有3600秒的延遲
如果主庫有數據誤刪了,只需要再多追1小時數據就可以

rm 刪除數據
除非是惡意刪除整個集羣,單獨刪除一個機器的數據並不怕,對於高可用的MySQL來說,隨便從其他節點就可以把數據恢復出來了

預防措施

  1. 賬號分離
  2. 定製操作規範,刪除操作必須通過管理系統執行
  3. 加上sql_safe_updates,delete和update語句中沒有where會報錯
  4. 經常演練誤刪除恢復操作,把這個做成自動化
     

 

kill不掉的語句

mysql中有兩個kill命令
kill query + 線程id,表示終止這個線程正在執行的語句
kill connection + 線程id,表示斷開這個線程的連接,connection可以省略
通過下面命令可以查看正在運行的線程

show processlist

kill query 類似linux的信號,並不是直接kill,而且告訴進程將要終止了,後面還有一些收尾操作
kill操作會做下面事情

  1. 把運行的狀態改成THD::KILL_QUERY
  2. 給執行的線程發送一個信號

這裏有三層含義

  1. 一個語句執行過程中會有多個埋點,埋點的地方判斷線程狀態,如果發現線程狀態是THD::KILL_QUERY,纔開始終止邏輯
  2. 如果處於等待狀態,必須是一個可以被喚醒的等待,否則根不會執行到“埋點”處
  3. 語句從開始進入終止邏輯,到終止邏輯完全完成,是有一個過程的

所以,不是說停就能停的

所以kill無效的一個情況是

  1. 線程沒有到判斷線程狀態的邏輯
  2. 終止邏輯耗時很長

如:超大事務被kill需要做很多回滾操作
大查詢回滾,如果中間生成了較大的臨時文件,加上文件系統壓力大,刪除臨時文件可能要等待IO資源導致耗時長
DDL命令執行到最後被kill,需要刪除中間的臨時文件,也可能受IO資源影響

另一個誤解
如果數據庫的表很多,連接過程就會很慢
連接過程包含了驗證邏輯,跟表的數量無關,慢是因爲客戶端連接完後,會將所有的表在本地構建一個hash表,做自動補全用
所以並不是連接慢,是客戶端慢,加上-A,將這個自動補全去掉即可

 

大批量數據查詢

比如內存只有100G,但是數據有200G,如果全讀到內存,整個機器內存都不夠,進程會被直接OOM
比如下面這個語句,執行全表掃描
mysql -h$host -P$port -u$user -p$pwd -e "select * from db1.t" > $target_file

mysql並不是全讀到內存再發送到客戶端的,而是邊讀邊發
如下圖所屬

執行流程是
1.讀取一行寫入到 net_buffer中,這個大小由net_buffer_length定義,默認是16K
2.重複上述步驟,直到net_buffer寫滿,調用網絡接口發出去
3.如果發送成功就清空net_buffer,繼續讀取下一行
4.如果失敗就表示本地網絡棧滿了,進入等待狀態

可以看到,一個查詢在發送過程中,佔用mysql內部最大值就是net_buffer_length,不會達到200G
socket send buffer也不會達到200G(默認定義 /proc/sys/net/core/wmen_default),如果網絡的buffer被寫滿,就會暫停讀取數據

通過
show processlist 查看
僅當一個線程處於【等待客戶端接收結果】的狀態,才顯示【Sending to client】
如果顯示成【Sending data】,他的意思是正在執行,比如可能卡在等待鎖上了

InnoDB有 Buffer Pool,數據的讀寫都是在內存,一般好的系統,內存命中率在99%
如果數據放不下了,使用LRU算法淘汰舊的數據,但這種淘汰算法不是普通的LRU,是經過修改後的
修改後的LRU圖,使用過程如下


InnoDB 按照5:3的比例把整個LRU鏈分成young區和old區
上圖中
1.訪問數據頁P3,由於P3在young區,所以跟普通的LRU一樣,將P3移動到鏈表頭部
2.之後訪問一個不存在的數據頁,會淘汰掉最後的數據頁Pm,但新插入的數據頁Px,是放在LRU old區
3.處於old區的數據頁,每次被訪問都要做如下判斷
若這個數據頁在LRU鏈表中存在的時間超過了1秒,就把它移動到頭部
如果這個這個數據頁在LRU鏈表中的時間短於1秒,則保持不變,這個參數由
innodb_old_blocks_time控制

這個策略,就是爲了類似全表掃描而量身定時的,改進後的LRU操作邏輯

  1. 掃描過程中,新插入的數據頁放到old區
  2. 由於順序掃描這個數據頁第一次和最後一次被訪問的時間間隔不會超過1秒,因此還保留在old區
  3. 繼續掃描之後的數據,之前這個數據頁再也不會被訪問到,也沒有機會移動到鏈表頭部young區,很快就被淘汰出去

這個策略也用到了Buffer Pool,但對young區完全沒影響,從而保證了Buffer Pool響應正常業務的查詢命中率
 

join原理

Index Nested-Loop Join
使用普通的join,mysql會自動優化選一個表做驅動表,一個表做被驅動表,這裏手動指定
select * from t1 straight_join t2 on (t1.a=t2.a);

join的執行過程如下圖

  1. 從表t1中讀取一行數據R
  2. 從數據行R中,取出a字段到表t2中去查
  3. 取出表t2中滿足條件的行,跟R組成一行,作爲結果集的一部分
  4. 重複執行步驟1-3,直到表t1的末尾循環結束

t1是驅動表,t2是被驅動表,整個過程中t2會使用到索引

對於t1是全表掃描,但t2走的是索引就會很快
所以t1應該是小表更合適,這樣t2走索引效率就很高了

Block Nested-Loop Join
對於被驅動表,沒有索引只能走全表掃描了
流程如下圖

上圖中的流程如下
1.把表t1的數據讀入線程內存join_buffer中,由於我們這個語句中寫的是select *,因此把整個t1表放入內存
2.掃描t2,把表t2中的每一行取出來,跟join_buffer 中的數據做對比,滿足join條件的,作爲結果集的一部分返回

默認情況下是放內存執行的,如果t1表是10W,t2表是10W
結果就是10W*10W,100億次,因爲是在內存執行的所以速度很酷開
如果內存中放不下t1的全表內容,則需要將t1表分段執行
執行如下圖

執行過程如下

  1. 掃描t1,順序讀取數據行放入join_buffer中,等join_buffer滿了,繼續第二步
  2. 掃描t2,把t2中的每一行取出來,跟join_buffer中的數據做對比,滿足join條件的作爲結果的一部分返回
  3. 清空join_buffer
  4. 繼續掃描t1,順序讀取到剩下的行放入到join_buffer中,繼續執行第二步

對於join語句的使用

  1. 如果可以使用索引,那就是Index Nested-Loop Join算法,這是沒問題的
  2. 如果沒有索引,走的是Block Nested-Loop Join算法,這樣被驅動的表會被掃描多次,佔用大量系統資源,這種join不要使用

所以判斷是否要用join,就看explain裏面,Extra字段裏面是否有【Block Nested-Loop Join】
對於大表小表的選擇是,驅動表總應該是使用 小表做驅動
所謂的大表,小表不是說這個表數據量多大
應該是兩個表按各種的過濾條件,過濾完之後,計算參與join的各個字段的總數據量,數據量小的那個表,就是【小表】,應該作爲驅動表

 

join優化

Multi-Range Read優化
這個優化的目的是儘量使用順序讀盤
比如這個語句會出現回表
select * from t1 where a>=1 and a<=100;

回表流程如下

如果隨着a的值遞增順序查詢的話,id的值就會變成隨機的,那麼就會出現隨機訪問
如果按照主鍵順序查詢的話,對磁盤的讀比較接近順序讀,能夠提升讀性能
MMR優化後執行過程如下

執行過程如下
1.根據索引a,定位到滿足條件的記錄,將id值放入read_rnd_buffer中
2.將read_rnd_buffer中的id進行遞增排序
3.排序後的id數組,依次到主鍵id索引中查記錄,並作爲結果返回

如果explain中有MMR,說明使用了MRR優化

MRR的核心在於,這條查詢語句在索引a上做的是一個範圍查詢,也就是一個多值查詢,可以得到最夠多的主鍵id,這樣通過排序後,再去主鍵索引樹上查數據,才能體現出順序性的優勢

Batched Key Access
這個優化方式是,一次性的多從t1裏面拿一些數據出來,一起傳給t2
先從t1裏面取一些數據出來,放到一個臨時表,這個臨時內容就是join_buffer
執行如下圖

在join_buffer中放入的數據是p1--p100,表示只會取查詢需要的字段,如果join_buffer放不下所有數據,就會把這1--行數據分成多段執行上圖流程
要使用BKA算法,需要執行下面sql

set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

BAK算法優化需要依賴MRR

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章