女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

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

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一個(gè)由于MySQL分頁導(dǎo)致的線上事故

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

背景

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

突然電話響了起來,一看是我們的一個(gè)運(yùn)維同學(xué),頓時(shí)緊張了起來,本周的版本已經(jīng)發(fā)布過了,這時(shí)候打電話一般來說是線上出問題了。

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

好吧,這問題算是嚴(yán)重了,匆匆趕到家后打開電腦,跟同事把 Pinpoint 上的慢查詢?nèi)罩緭瞥鰜怼?/span>

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

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

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

詳細(xì)對比日志發(fā)現(xiàn),很多分頁的時(shí)間是重疊的,對方應(yīng)該是多線程調(diào)用。

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

分析

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

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

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

這種查詢的慢,其實(shí)是因?yàn)?limit 后面的偏移量太大導(dǎo)致的。

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

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

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


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

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

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

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

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

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


/*產(chǎn)生隨機(jī)部門編號的函數(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把a(bǔ)utocommit設(shè)置成0,把默認(rèn)提交關(guān)閉*/
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);

⑤建立關(guān)鍵字段的索引,這邊是跑完數(shù)據(jù)之后再建索引,會導(dǎo)致建索引耗時(shí)長,但是跑數(shù)據(jù)就會快一些。
/*建立關(guān)鍵字段的索引:排序、條件*/
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í)行結(jié)果:
[SQL]
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depnoorderbya.iddesclimit100,25;
受影響的行:0
時(shí)間:0.001s
[SQL]
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depnoorderbya.iddesclimit4800000,25;
受影響的行:0
時(shí)間:12.275s

因?yàn)閽呙璧臄?shù)據(jù)多,所以這個(gè)明顯不是一個(gè)量級上的耗時(shí)。

解決方案

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

因?yàn)槲覀冇兄麈I id,并且在上面建了索引,所以可以先在索引樹中找到開始位置的 id 值,再根據(jù)找到的 id 值查詢行數(shù)據(jù)。
/*子查詢獲取偏移100條的位置的id,在這個(gè)位置上往后取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,在這個(gè)位置上往后取25*/
SELECTa.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depno
wherea.id>=(selectidfromemporderbyidlimit4800000,1)
orderbya.idlimit25;

執(zhí)行結(jié)果

執(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
時(shí)間: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
時(shí)間:1.541s

②起始位置重定義

記住上次查找結(jié)果的主鍵位置,避免使用偏移量 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í)行結(jié)果:
[SQL]
SELECTa.id,a.empno,a.empname,a.job,a.sal,b.depno,b.depname
fromempaleftjoindepbona.depno=b.depno
wherea.id>100orderbya.idlimit25;
受影響的行:0
時(shí)間: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
時(shí)間:0.000s

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

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

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

③降級策略

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

因?yàn)樗X得超過這個(gè)值你已經(jīng)不是在分頁了,而是在刷數(shù)據(jù)了,如果確認(rèn)要找數(shù)據(jù),應(yīng)該輸入合適條件來縮小范圍,而不是一頁一頁分頁。

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

小結(jié)

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

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

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

審核編輯 :李倩


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

    關(guān)注

    7

    文章

    3900

    瀏覽量

    65753
  • MySQL
    +關(guān)注

    關(guān)注

    1

    文章

    849

    瀏覽量

    27512

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

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

收藏 人收藏

    評論

    相關(guān)推薦
    熱點(diǎn)推薦

    從Delphi、C++ Builder和Lazarus連接到MySQL數(shù)據(jù)庫

    ? 從 Delphi、C++ Builder 和 Lazarus 連接到 MySQL 數(shù)據(jù)庫 MySQL 數(shù)據(jù)訪問組件(MyDAC)是個(gè)組件庫,提供從 Delphi 和 C++ Bu
    的頭像 發(fā)表于 01-20 13:47 ?583次閱讀
    從Delphi、C++ Builder和Lazarus連接到<b class='flag-5'>MySQL</b>數(shù)據(jù)庫

    使用插件將Excel連接到MySQL/MariaDB

    ,可以快速地將數(shù)據(jù)從 MySQL 或 MariaDB 加載到 Excel,立即從數(shù)據(jù)庫刷新 Excel 工作簿中的數(shù)據(jù),編輯這些數(shù)據(jù),并將它們保存回 MySQL。之后您能夠像使用的 Excel 工作表
    的頭像 發(fā)表于 01-20 12:38 ?508次閱讀
    使用插件將Excel連接到<b class='flag-5'>MySQL</b>/MariaDB

    適用于MySQL和MariaDB的.NET連接器

    支持 ORM 的適用于 MySQL 和 MariaDB 的 .NET 連接器 dotConnect for MySQL種高性能 ADO.NET 數(shù)據(jù)提供程序,可在開發(fā) MySQL
    的頭像 發(fā)表于 01-16 14:17 ?378次閱讀
    適用于<b class='flag-5'>MySQL</b>和MariaDB的.NET連接器

    MySQL數(shù)據(jù)庫的安裝

    MySQL個(gè)開源免費(fèi)的關(guān)系型數(shù)據(jù)庫管理系統(tǒng),由瑞典MySQL AB 公司開發(fā),目前屬于 Oracle 旗下公司。 MySQL 最流行的
    的頭像 發(fā)表于 01-14 11:25 ?477次閱讀
    <b class='flag-5'>MySQL</b>數(shù)據(jù)庫的安裝

    什么是虛擬內(nèi)存分頁 Windows系統(tǒng)虛擬內(nèi)存優(yōu)化方法

    虛擬內(nèi)存分頁概述 在Windows操作系統(tǒng)中,虛擬內(nèi)存是通過分頁機(jī)制實(shí)現(xiàn)的。分頁允許系統(tǒng)將內(nèi)存中的數(shù)據(jù)移動到硬盤上,以便為當(dāng)前運(yùn)行的程序騰出空間。這個(gè)過程對于保持系統(tǒng)的流暢運(yùn)行至關(guān)重要,尤其是在物理
    的頭像 發(fā)表于 12-04 09:16 ?1253次閱讀

    MySQL還能跟上PostgreSQL的步伐嗎

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

    詳解MySQL多實(shí)例部署

    詳解MySQL多實(shí)例部署
    的頭像 發(fā)表于 11-11 11:10 ?557次閱讀

    MySQL編碼機(jī)制原理

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

    mysql定時(shí)備份任務(wù)

    在生產(chǎn)環(huán)境上,為了避免數(shù)據(jù)的丟失,通常情況下都會定時(shí)的對數(shù)據(jù)庫進(jìn)行備份。而Linux的crontab指令則可以幫助我們實(shí)現(xiàn)對數(shù)據(jù)庫定時(shí)進(jìn)行備份。首先我們來簡單了解crontab指令,如果你會了請?zhí)较?b class='flag-5'>一個(gè)內(nèi)容mysql備份。
    的頭像 發(fā)表于 10-31 10:07 ?448次閱讀

    適用于MySQL的dbForge架構(gòu)比較

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

    MySQL性能優(yōu)化淺析及線上案例

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

    Jtti:MySQL初始化操作如何設(shè)置root密碼

    MySQL初始化時(shí),可以通過以下步驟設(shè)置root密碼: 打開命令行工具,使用以下命令啟動MySQL服務(wù): ? sudo service mysql start ? 使用以下命令登錄MySQL
    的頭像 發(fā)表于 08-08 16:45 ?727次閱讀

    MySQL知識點(diǎn)匯總

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

    文了解MySQL索引機(jī)制

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

    華納云:如何修改MySQL的默認(rèn)端口

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