在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

一個由于MySQL分頁導致的線上事故

Android編程精選 ? 來源:JAVA日知錄 ? 作者:JAVA日知錄 ? 2022-05-10 15:31 ? 次閱讀
今天給大家分享個生產事故,一個由于 MySQL 分頁導致的線上事故,事情是這樣的~

背景

一天晚上 10 點半,下班后愉快的坐在在回家的地鐵上,心里想著周末的生活怎么安排。

突然電話響了起來,一看是我們的一個運維同學,頓時緊張了起來,本周的版本已經發布過了,這時候打電話一般來說是線上出問題了。

果然,溝通的情況是線上的一個查詢數據的接口被瘋狂的失去理智般的調用,這個操作直接導致線上的 MySQL 集群被拖慢了。

好吧,這問題算是嚴重了,匆匆趕到家后打開電腦,跟同事把 Pinpoint 上的慢查詢日志撈出來。

看到一個很奇怪的查詢,如下:
1POSTdomain/v1.0/module/method?order=condition&orderType=desc&offset=1800000&limit=500

domain、module 和 method 都是化名,代表接口的域、模塊和實例方法名,后面的 offset 和 limit 代表分頁操作偏移量和每頁的數量,也就是說該同學是在翻第(1800000/500+1=3601)頁。初步撈了一下日志,發現有 8000 多次這樣調用。

這太神奇了,而且我們頁面上的分頁單頁數量也不是 500,而是 25 條每頁,這個絕對不是人為的在功能頁面上進行一頁一頁的翻頁操作,而是數據被刷了(說明下,我們生產環境數據有 1 億+)。

詳細對比日志發現,很多分頁的時間是重疊的,對方應該是多線程調用。

通過對鑒權的 Token 的分析,基本定位了請求是來自一個叫做 ApiAutotest 的客戶端程序在做這個操作,也定位了生成鑒權 Token 的賬號來自一個 QA 的同學。立馬打電話給同學,進行了溝通和處理。

分析

其實對于我們的 MySQL 查詢語句來說,整體效率還是可以的,該有的聯表查詢優化都有,該簡略的查詢內容也有,關鍵條件字段和排序字段該有的索引也都在,問題在于他一頁一頁的分頁去查詢,查到越后面的頁數,掃描到的數據越多,也就越慢。

我們在查看前幾頁的時候,發現速度非常快,比如 limit 200,25,瞬間就出來了。但是越往后,速度就越慢,特別是百萬條之后,卡到不行,那這個是什么原理呢。

先看一下我們翻頁翻到后面時,查詢的 sql 是怎樣的:
1select*fromt_namewherec_name1='xxx'orderbyc_name2limit2000000,25;

這種查詢的慢,其實是因為 limit 后面的偏移量太大導致的。

比如像上面的 limit 2000000,25,這個等同于數據庫要掃描出 2000025 條數據,然后再丟棄前面的 20000000 條數據,返回剩下 25 條數據給用戶,這種取法明顯不合理。

4eeb5890-cf8d-11ec-bce3-dac502259ad0.png

大家翻看《高性能 MySQL》第六章:查詢性能優化,對這個問題有過說明:分頁操作通常會使用 limit 加上偏移量的辦法實現,同時再加上合適的 order by 子句。


這會出現一個常見問題:當偏移量非常大的時候,它會導致 MySQL 掃描大量不需要的行然后再拋棄掉。

數據模擬

那好,了解了問題的原理,那就要試著解決它了。涉及數據敏感性,我們這邊模擬一下這種情況,構造一些數據來做測試。

①創建兩個表:員工表和部門表
/*部門表,存在則進行刪除*/
droptableifEXISTSdep;
createtabledep(
idintunsignedprimarykeyauto_increment,
depnomediumintunsignednotnulldefault0,
depnamevarchar(20)notnulldefault"",
memovarchar(200)notnulldefault""
);

/*員工表,存在則進行刪除*/
droptableifEXISTSemp;
createtableemp(
idintunsignedprimarykeyauto_increment,
empnomediumintunsignednotnulldefault0,
empnamevarchar(20)notnulldefault"",
jobvarchar(9)notnulldefault"",
mgrmediumintunsignednotnulldefault0,
hiredatedatetimenotnull,
saldecimal(7,2)notnull,
comndecimal(7,2)notnull,
depnomediumintunsignednotnulldefault0
);

②創建兩個函數:生成隨機字符串和隨機編號
/*產生隨機字符串的函數*/
DELIMITER$
dropFUNCTIONifEXISTSrand_string;
CREATEFUNCTIONrand_string(nINT)RETURNSVARCHAR(255)
BEGIN
DECLAREchars_strVARCHAR(100)DEFAULT'abcdefghijklmlopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
DECLAREreturn_strVARCHAR(255)DEFAULT'';
DECLAREiINTDEFAULT0;
WHILEiDO
SETreturn_str=CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SETi=i+1;
ENDWHILE;
RETURNreturn_str;
END$
DELIMITER;


/*產生隨機部門編號的函數*/
DELIMITER$
dropFUNCTIONifEXISTSrand_num;
CREATEFUNCTIONrand_num()RETURNSINT(5)
BEGIN
DECLAREiINTDEFAULT0;
SETi=FLOOR(100+RAND()*10);
RETURNi;
END$
DELIMITER;

③編寫存儲過程,模擬 500W 的員工數據
/*建立存儲過程:往emp表中插入數據*/
DELIMITER$
dropPROCEDUREifEXISTSinsert_emp;
CREATEPROCEDUREinsert_emp(INSTARTINT(10),INmax_numINT(10))
BEGIN
DECLAREiINTDEFAULT0;
/*setautocommit=0把autocommit設置成0,把默認提交關閉*/
SETautocommit=0;
REPEAT
SETi=i+1;
INSERTINTOemp(empno,empname,job,mgr,hiredate,sal,comn,depno)VALUES((START+i),rand_string(6),'SALEMAN',0001,now(),2000,400,rand_num());
UNTILi=max_num
ENDREPEAT;
COMMIT;
END$
DELIMITER;
/*插入500W條數據*/
callinsert_emp(0,5000000);

④編寫存儲過程,模擬 120 的部門數據
/*建立存儲過程:往dep表中插入數據*/
DELIMITER$
dropPROCEDUREifEXISTSinsert_dept;
CREATEPROCEDUREinsert_dept(INSTARTINT(10),INmax_numINT(10))
BEGIN
DECLAREiINTDEFAULT0;
SETautocommit=0;
REPEAT
SETi=i+1;
INSERTINTOdep(depno,depname,memo)VALUES((START+i),rand_string(10),rand_string(8));
UNTILi=max_num
ENDREPEAT;
COMMIT;
END$
DELIMITER;
/*插入120條數據*/
callinsert_dept(1,120);

⑤建立關鍵字段的索引,這邊是跑完數據之后再建索引,會導致建索引耗時長,但是跑數據就會快一些。
/*建立關鍵字段的索引:排序、條件*/
CREATEINDEXidx_emp_idONemp(id);
CREATEINDEXidx_emp_depnoONemp(depno);
CREATEINDEXidx_dep_depnoONdep(depno);

測試

測試數據:
/*偏移量為100,取25*/
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depnoorderbya.iddesclimit100,25;
/*偏移量為4800000,取25*/
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depnoorderbya.iddesclimit4800000,25;

執行結果:
[SQL]
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depnoorderbya.iddesclimit100,25;
受影響的行:0
時間:0.001s
[SQL]
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depnoorderbya.iddesclimit4800000,25;
受影響的行:0
時間:12.275s

因為掃描的數據多,所以這個明顯不是一個量級上的耗時。

解決方案

①使用索引覆蓋+子查詢優化

因為我們有主鍵 id,并且在上面建了索引,所以可以先在索引樹中找到開始位置的 id 值,再根據找到的 id 值查詢行數據。
/*子查詢獲取偏移100條的位置的id,在這個位置上往后取25*/
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depno
wherea.id>=(selectidfromemporderbyidlimit100,1)
orderbya.idlimit25;

/*子查詢獲取偏移4800000條的位置的id,在這個位置上往后取25*/
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depno
wherea.id>=(selectidfromemporderbyidlimit4800000,1)
orderbya.idlimit25;

執行結果

執行效率相比之前有大幅的提升:
[SQL]
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depno
wherea.id>=(selectidfromemporderbyidlimit100,1)
orderbya.idlimit25;
受影響的行:0
時間:0.106s

[SQL]
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depno
wherea.id>=(selectidfromemporderbyidlimit4800000,1)
orderbya.idlimit25;
受影響的行:0
時間:1.541s

②起始位置重定義

記住上次查找結果的主鍵位置,避免使用偏移量 offset:
/*記住了上次的分頁的最后一條數據的id是100,這邊就直接跳過100,從101開始掃描表*/
SELECTa.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depno
wherea.id>100orderbya.idlimit25;

/*記住了上次的分頁的最后一條數據的id是4800000,這邊就直接跳過4800000,從4800001開始掃描表*/
SELECTa.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depno
wherea.id>4800000
orderbya.idlimit25;

執行結果:
[SQL]
SELECTa.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depno
wherea.id>100orderbya.idlimit25;
受影響的行:0
時間:0.001s

[SQL]
SELECTa.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depno
wherea.id>4800000
orderbya.idlimit25;
受影響的行:0
時間:0.000s

這個效率是最好的,無論怎么分頁,耗時基本都是一致的,因為他執行完條件之后,都只掃描了 25 條數據。

但是有個問題,只適合一頁一頁的分頁,這樣才能記住前一個分頁的最后 id。如果用戶跳著分頁就有問題了,比如剛剛刷完第 25 頁,馬上跳到 35 頁,數據就會不對。

這種的適合場景是類似百度搜索或者騰訊新聞那種滾輪往下拉,不斷拉取不斷加載的情況。這種延遲加載會保證數據不會跳躍著獲取。

③降級策略

看了網上一個阿里的 DBA 同學分享的方案:配置 limit 的偏移量和獲取數一個最大值,超過這個最大值,就返回空數據。

因為他覺得超過這個值你已經不是在分頁了,而是在刷數據了,如果確認要找數據,應該輸入合適條件來縮小范圍,而不是一頁一頁分頁。

這個跟我同事的想法大致一樣:request 的時候如果 offset 大于某個數值就先返回一個 4xx 的錯誤。

小結

當晚我們應用上述第三個方案,對 offset 做一下限流,超過某個值,就返回空值。第二天使用第一種和第二種配合使用的方案對程序和數據庫腳本進一步做了優化。合理來說做任何功能都應該考慮極端情況,設計容量都應該涵蓋極端邊界測試。

另外,該有的限流、降級也應該考慮進去。比如工具多線程調用,在短時間頻率內 8000 次調用,可以使用計數服務判斷并反饋用戶調用過于頻繁,直接給予斷掉。

哎,大意了啊,搞了半夜,QA 同學不講武德。

審核編輯 :李倩


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 數據庫
    +關注

    關注

    7

    文章

    3826

    瀏覽量

    64509
  • MySQL
    +關注

    關注

    1

    文章

    817

    瀏覽量

    26628

原文標題:一次線上MySQL分頁事故,搞了半夜...

文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    什么是虛擬內存分頁 Windows系統虛擬內存優化方法

    虛擬內存分頁概述 在Windows操作系統中,虛擬內存是通過分頁機制實現的。分頁允許系統將內存中的數據移動到硬盤上,以便為當前運行的程序騰出空間。這個過程對于保持系統的流暢運行至關重要,尤其是在物理
    的頭像 發表于 12-04 09:16 ?425次閱讀

    MySQL還能跟上PostgreSQL的步伐嗎

    Percona 的老板 Peter Zaitsev最近發表篇博客,討論了MySQL是否還能跟上PostgreSQL的腳步。Percona 作為MySQL 生態扛旗者,Percona 開發了知名
    的頭像 發表于 11-18 10:16 ?229次閱讀
    <b class='flag-5'>MySQL</b>還能跟上PostgreSQL的步伐嗎

    詳解MySQL多實例部署

    詳解MySQL多實例部署
    的頭像 發表于 11-11 11:10 ?279次閱讀

    MySQL編碼機制原理

    前言 位讀者在本地部署 MySQL 測試環境時碰到問題,我覺得挺有代表性的,所以寫篇文章介紹下,看完相信你會對
    的頭像 發表于 11-09 11:01 ?261次閱讀

    適用于MySQL的dbForge架構比較

    dbForge Schema Compare for MySQL種工具,用于輕松有效地比較和部署 MySQL 數據庫結構和腳本文件夾差異。該工具提供了 MySQL 數據庫架構中所
    的頭像 發表于 10-28 09:41 ?226次閱讀
    適用于<b class='flag-5'>MySQL</b>的dbForge架構比較

    MySQL性能優化淺析及線上案例

    作者:京東健康 孟飛 1、 數據庫性能優化的意義 業務發展初期,數據庫中量般都不高,也不太容易出些性能問題或者出的問題也不大,但是當數據庫的量級達到定規模之后,如果缺失有效的預警、監控、處理等
    的頭像 發表于 10-22 15:17 ?705次閱讀
    <b class='flag-5'>MySQL</b>性能優化淺析及<b class='flag-5'>線上</b>案例

    MySQL知識點匯總

    大家好,這部分被稱為DQL部分,是每個學習MySQL必須要學會的部分,下面就讓我來介紹MySQL中的其他部分。
    的頭像 發表于 08-05 15:27 ?416次閱讀
    <b class='flag-5'>MySQL</b>知識點匯總

    文了解MySQL索引機制

    接觸MySQL數據庫的小伙伴定避不開索引,索引的出現是為了提高數據查詢的效率,就像書的目錄樣。 某一個SQL查詢比較慢,你第時間想到的
    的頭像 發表于 07-25 14:05 ?304次閱讀
    <b class='flag-5'>一</b>文了解<b class='flag-5'>MySQL</b>索引機制

    華納云:如何修改MySQL的默認端口

    MySQL是世界上最流行的開源關系型數據庫管理系統之。在某些情況下,由于安全性、網絡策略或端口沖突的原因,數據庫管理員可能需要更改MySQL服務的默認監聽端口。本文將指導您如何在不同
    的頭像 發表于 07-22 14:56 ?331次閱讀
    華納云:如何修改<b class='flag-5'>MySQL</b>的默認端口

    MySQL的整體邏輯架構

    支持多種存儲引擎是眾所周知的MySQL特性,也是MySQL架構的關鍵優勢之。如果能夠理解MySQL Server與存儲引擎之間是怎樣通過API交互的,將大大有利于理解
    的頭像 發表于 04-30 11:14 ?466次閱讀
    <b class='flag-5'>MySQL</b>的整體邏輯架構

    信號線上小電阻干啥用的?

    ? 很多時候,高速數字信號傳輸線上會串電阻,目的是解決阻抗匹配問題,阻抗不匹配會導致信號反射、過沖等問題。 電磁波類似光樣在同種介質中傳播方向和能量不會衰減,但如果光從
    發表于 04-26 09:31

    MySQL忘記root密碼解決方案

    mysql登錄密碼為password()算法加密,解密成本太高,以下為通用方案; 原理:mysql提供了特殊啟動方式,即跳過權限表驗證,啟動后,登錄不需要提供密碼; 登錄后,即可修改mysql數據庫的user表,重置
    的頭像 發表于 04-23 16:08 ?730次閱讀

    查詢SQL在mysql內部是如何執行?

    我們知道在mySQL客戶端,輸入條查詢SQL,然后看到返回查詢的結果。這條查詢語句在 MySQL 內部到底是如何執行的呢?本文跟大家探討下哈,我們先來看下
    的頭像 發表于 01-22 14:53 ?592次閱讀
    查詢SQL在<b class='flag-5'>mysql</b>內部是如何執行?

    阿里二面:了解MySQL事務底層原理嗎

    MySQL 是如何來解決臟寫這種問題的?沒錯,就是鎖。MySQL 在開啟事務的時候,他會將某條記錄和事務做一個綁定。這個其實和 JV
    的頭像 發表于 01-18 16:34 ?347次閱讀
    阿里二面:了解<b class='flag-5'>MySQL</b>事務底層原理嗎

    MySQL密碼忘記了怎么辦?MySQL密碼快速重置方法步驟命令示例!

    MySQL密碼忘記了怎么辦?MySQL密碼快速重置方法步驟命令示例! MySQL種常用的關系型數據庫管理系統,如果你忘記了MySQL的密
    的頭像 發表于 01-12 16:06 ?773次閱讀
    主站蜘蛛池模板: 午夜操| 天天草比| 国产真实乱在线更新| 午夜影视体验区| 天堂最新版在线www在线| 日日夜夜爽| 国产精品japanese人妖| 1024手机免费看片| 天天干天天摸天天操| 手机看片福利久久| 成人理伦| 亚洲视频入口| 最好看免费中文字幕2018视频| 中文字幕11页| 色天使色婷婷在线影院亚洲| 你懂的在线免费观看| 国产98色在线| 天天操综合网| 色综合美国色农夫网| 婷婷在线网站| 五月婷婷一区二区| 免费毛片软件| 丁香花在线观看免费观看| 图片视频小说| 在线免费观看色片| 久久久久国产一级毛片高清片| 午夜视频色| 美女免费视频是黄的| 中文字幕v视界影院| 精品理论片| 美女视频一区二区| 日韩高清毛片| video另类蛇交| 国产无套视频在线观看香蕉| 亚洲一区二区影视| 四虎永久在线精品影院| 精品色图| 欧美影欧美影院免费观看视频| 182.t v香蕉人人网站| 亚洲aaa视频| 天堂在线中文无弹窗全文阅读|