91欧美超碰AV自拍|国产成年人性爱视频免费看|亚洲 日韩 欧美一厂二区入|人人看人人爽人人操aV|丝袜美腿视频一区二区在线看|人人操人人爽人人爱|婷婷五月天超碰|97色色欧美亚州A√|另类A√无码精品一级av|欧美特级日韩特级

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

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

3天內不再提示

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

Android編程精選 ? 來源:JAVA日知錄 ? 作者:JAVA日知錄 ? 2022-05-10 15:31 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

今天給大家分享個生產事故,一個由于 MySQL 分頁導致的線上事故,事情是這樣的~

背景

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

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

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

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

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

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

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

詳細對比日志發(fā)現(xiàn),很多分頁的時間是重疊的,對方應該是多線程調用。

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

分析

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

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

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

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

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

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

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


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

數(shù)據(jù)模擬

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

①創(chuàng)建兩個表:員工表和部門表
/*部門表,存在則進行刪除*/
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
);

②創(chuàng)建兩個函數(shù):生成隨機字符串和隨機編號
/*產生隨機字符串的函數(shù)*/
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;


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

③編寫存儲過程,模擬 500W 的員工數(shù)據(jù)
/*建立存儲過程:往emp表中插入數(shù)據(jù)*/
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條數(shù)據(jù)*/
callinsert_emp(0,5000000);

④編寫存儲過程,模擬 120 的部門數(shù)據(jù)
/*建立存儲過程:往dep表中插入數(shù)據(jù)*/
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條數(shù)據(jù)*/
callinsert_dept(1,120);

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

測試

測試數(shù)據(jù):
/*偏移量為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;

執(zhí)行結果:
[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

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

解決方案

①使用索引覆蓋+子查詢優(yōu)化

因為我們有主鍵 id,并且在上面建了索引,所以可以先在索引樹中找到開始位置的 id 值,再根據(jù)找到的 id 值查詢行數(shù)據(jù)。
/*子查詢獲取偏移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;

執(zhí)行結果

執(zhí)行效率相比之前有大幅的提升:
[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:
/*記住了上次的分頁的最后一條數(shù)據(jù)的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;

/*記住了上次的分頁的最后一條數(shù)據(jù)的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;

執(zhí)行結果:
[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

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

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

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

③降級策略

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

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

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

小結

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

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

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

審核編輯 :李倩


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

    關注

    7

    文章

    4020

    瀏覽量

    68369
  • MySQL
    +關注

    關注

    1

    文章

    906

    瀏覽量

    29558

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

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

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    MySQL事務與鎖機制詳解

    在我擔任某互聯(lián)網(wǎng)金融平臺SRE期間,曾遇到過次嚴重的線上事故:凌晨3點,監(jiān)控系統(tǒng)瘋狂告警,數(shù)據(jù)庫活躍連接數(shù)從平時的200飆升到2000,大量請求超時。緊急排查后發(fā)現(xiàn),
    的頭像 發(fā)表于 01-27 10:33 ?273次閱讀

    MySQL關鍵參數(shù)的最佳配置

    運維MySQL數(shù)據(jù)庫十年有余,見過太多因為參數(shù)配置不當導致的性能問題。有的公司用著默認配置跑生產環(huán)境,128GB內存的服務器上InnoDB緩沖池只有128MB;有的把max_connections設成幾萬,結果OOM把數(shù)據(jù)庫打掛;還有的sync_binlog設成1,卻抱怨
    的頭像 發(fā)表于 01-27 10:32 ?350次閱讀

    恒訊科技解析:如何安裝MySQL并創(chuàng)建數(shù)據(jù)庫

    安裝和管理MySQL不必復雜。只需幾分鐘,你就能在Linux服務器上搭建MySQL,創(chuàng)建第一個數(shù)據(jù)庫,甚至自動化備份——同時確保數(shù)據(jù)安全有序。 什么是 MySQL?
    的頭像 發(fā)表于 01-14 14:25 ?180次閱讀

    Mysql數(shù)據(jù)恢復—Windows Server下MySQL(InnoDB)全表誤刪數(shù)據(jù)恢復案例

    本地服務器,操作系統(tǒng)為windows server。服務器上部署mysql單實例,innodb引擎,獨立表空間。未進行數(shù)據(jù)庫備份,未開啟binlog。 人為誤操作使用Delete命令刪除數(shù)據(jù)時未添加where子句,導致全表數(shù)據(jù)被刪除。刪除后未對該表進行任何操作。需要恢復
    的頭像 發(fā)表于 09-23 15:56 ?744次閱讀
    <b class='flag-5'>Mysql</b>數(shù)據(jù)恢復—Windows Server下<b class='flag-5'>MySQL</b>(InnoDB)全表誤刪數(shù)據(jù)恢復案例

    深度剖析Redis的兩大持久化機制

    凌晨3點,我被通緊急電話驚醒。線上Redis集群崩潰,6GB的緩存數(shù)據(jù)全部丟失,導致MySQL瞬間承壓暴增,整個交易系統(tǒng)陷入癱瘓。事后復盤發(fā)現(xiàn),問題的根源竟是
    的頭像 發(fā)表于 09-17 16:22 ?570次閱讀

    mysql數(shù)據(jù)恢復—mysql數(shù)據(jù)庫表被truncate的數(shù)據(jù)恢復案例

    某云ECS網(wǎng)站服務器,linux操作系統(tǒng),部署了mysql數(shù)據(jù)庫。工作人員在執(zhí)行數(shù)據(jù)庫版本更新測試時,錯誤地將本應在測試庫執(zhí)行的sql腳本在生產庫上執(zhí)行了,導致部分表被truncate,部分表內數(shù)據(jù)
    的頭像 發(fā)表于 09-11 09:28 ?880次閱讀
    <b class='flag-5'>mysql</b>數(shù)據(jù)恢復—<b class='flag-5'>mysql</b>數(shù)據(jù)庫表被truncate的數(shù)據(jù)恢復案例

    MySQL慢查詢終極優(yōu)化指南

    作為名在生產環(huán)境摸爬滾打多年的運維工程師,我見過太多因為慢查詢導致線上故障。今天分享套經過實戰(zhàn)檢驗的MySQL慢查詢分析與索引優(yōu)化方法
    的頭像 發(fā)表于 08-13 15:55 ?857次閱讀

    MySQL 8.0性能優(yōu)化實戰(zhàn)指南

    作為名運維工程師,MySQL數(shù)據(jù)庫優(yōu)化是我們日常工作中最具挑戰(zhàn)性的任務之。MySQL 8.0作為當前主流版本,在性能、安全性和功能上都有了顯著提升,但如何充分發(fā)揮其潛力,仍需要我們
    的頭像 發(fā)表于 07-24 11:48 ?866次閱讀

    MySQL數(shù)據(jù)備份與恢復策略

    數(shù)據(jù)是企業(yè)的核心資產,MySQL作為主流的關系型數(shù)據(jù)庫管理系統(tǒng),其數(shù)據(jù)的安全性和可靠性至關重要。本文將深入探討MySQL的數(shù)據(jù)備份策略、常用備份工具以及數(shù)據(jù)恢復的最佳實踐,幫助運維工程師構建完善的數(shù)據(jù)保護體系。
    的頭像 發(fā)表于 07-14 11:11 ?746次閱讀

    企業(yè)級MySQL數(shù)據(jù)庫管理指南

    在當今數(shù)字化時代,MySQL作為全球最受歡迎的開源關系型數(shù)據(jù)庫,承載著企業(yè)核心業(yè)務數(shù)據(jù)的存儲與處理。作為數(shù)據(jù)庫管理員(DBA),掌握MySQL的企業(yè)級部署、優(yōu)化、維護技能至關重要。本文將從實戰(zhàn)角度出發(fā),系統(tǒng)闡述MySQL在企業(yè)環(huán)
    的頭像 發(fā)表于 07-09 09:50 ?735次閱讀

    使用STM32H755ZIQ-NUCLEO時,由于數(shù)據(jù)線的原因導致固件升級失敗怎么解決?

    使用STM32H755ZIQ-NUCLEO時,由于數(shù)據(jù)線的原因導致固件升級失敗,目前沒有辦法下載程序,大佬們解決的辦法?
    發(fā)表于 06-17 06:47

    使用STM32H755ZIQ-NUCLEO時,由于數(shù)據(jù)線的原因導致固件升級失敗,怎么解決?

    使用STM32H755ZIQ-NUCLEO時,由于數(shù)據(jù)線的原因導致固件升級失敗,目前沒有辦法下載程序,大佬們解決的辦法?
    發(fā)表于 06-16 06:20

    MYSQL集群高可用和數(shù)據(jù)監(jiān)控平臺實現(xiàn)方案

    該項目共分為2子項目,由MYSQL集群高可用和數(shù)據(jù)監(jiān)控平臺兩部分組成。
    的頭像 發(fā)表于 05-28 10:10 ?1318次閱讀
    <b class='flag-5'>MYSQL</b>集群高可用和數(shù)據(jù)監(jiān)控平臺實現(xiàn)方案

    MySQL數(shù)據(jù)庫是什么

    MySQL數(shù)據(jù)庫是種 開源的關系型數(shù)據(jù)庫管理系統(tǒng)(RDBMS) ,由瑞典MySQL AB公司開發(fā),后被Oracle公司收購。它通過結構化查詢語言(SQL)進行數(shù)據(jù)存儲、管理和操作,廣泛應用于Web
    的頭像 發(fā)表于 05-23 09:18 ?1229次閱讀

    除了增刪改查你對MySQL還了解多少

    我們都知道MySQL服務器的默認端口為3306,之后就在這個端口號上等待客戶端進程進行連接(MySQL服務器會默認監(jiān)聽3306端口)。
    的頭像 發(fā)表于 04-14 17:20 ?738次閱讀