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

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

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

3天內不再提示

MySQL數(shù)據(jù)庫慢查詢分析與優(yōu)化實戰(zhàn)

馬哥Linux運維 ? 來源:馬哥Linux運維 ? 2026-04-02 09:38 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

MySQL數(shù)據(jù)庫慢查詢分析與優(yōu)化實戰(zhàn)

1 慢查詢的度量標準與配置

在討論MySQL慢查詢之前,需要先明確一個關鍵前提:什么是慢查詢?不同業(yè)務場景下,慢查詢的定義差異巨大。一個數(shù)據(jù)報表后臺的SQL執(zhí)行30秒可能屬于正常范圍,但一個訂單創(chuàng)建的數(shù)據(jù)庫操作超過100毫秒就可能造成用戶體驗問題。因此,慢查詢的度量必須結合具體業(yè)務場景。

通用度量標準是MySQL的slow_query_log,默認以10秒作為閾值記錄執(zhí)行時間超過該閾值的查詢。這一閾值可以通過long_query_time參數(shù)調整。

-- 查看當前慢查詢配置
SHOWVARIABLESLIKE'slow_query%';
SHOWVARIABLESLIKE'long_query_time';
SHOWVARIABLESLIKE'log_output';

-- 臨時開啟慢查詢日志(重啟后失效)
SETGLOBALslow_query_log ='ON';
SETGLOBALlong_query_time =2; -- 2秒
SETGLOBALlog_output ='FILE,TABLE'; -- 同時寫入文件和系統(tǒng)表
SETGLOBALslow_query_log_file ='/var/lib/mysql/mysql-slow.log';
SETGLOBALlog_queries_not_using_indexes ='ON'; -- 記錄未使用索引的查詢

-- 永久配置(寫入my.cnf)
-- [mysqld]
-- slow_query_log = 1
-- slow_query_log_file = /var/lib/mysql/mysql-slow.log
-- long_query_time = 2
-- log_queries_not_using_indexes = 1
-- min_examined_row_limit = 1000 -- 僅記錄掃描行數(shù)超過此值的查詢

log_output參數(shù)控制日志輸出目標。FILE將日志寫入文件系統(tǒng),TABLE將日志寫入mysql庫中的slow_log系統(tǒng)表(便于SQL查詢)。2026年的生產環(huán)境推薦同時啟用兩者:FILE用于實時分析,TABLE用于歸檔查詢。

log_queries_not_using_indexes是一個容易被誤解的參數(shù)。它只記錄未使用索引的查詢,但如果查詢的索引選擇率極低(如只匹配1%的數(shù)據(jù)),MySQL優(yōu)化器可能選擇全表掃描而非索引掃描——這種情況下log_queries_not_using_indexes不會記錄該查詢,但查詢仍然很慢。這是一個重要的盲區(qū),需要配合EXPLAIN結果綜合判斷。

2 slow_query_log分析工具鏈

2.1 pt-query-digest:生產環(huán)境首選

Percona Toolkit中的pt-query-digest是分析MySQL慢查詢最強大的工具。它能夠對慢查詢日志進行分組、排序、統(tǒng)計,識別出最需要優(yōu)化的查詢。

# 安裝Percona Toolkit
yum install percona-toolkit -y

# 基本分析
pt-query-digest /var/lib/mysql/mysql-slow.log

# 輸出到HTML報告(便于分享)
pt-query-digest --report-format=html 
 /var/lib/mysql/mysql-slow.log 
 > /tmp/slow_query_report.html

# 僅分析特定時間的查詢(排除預熱階段的查詢)
pt-query-digest 
 --since='2026-03-30 0600'
 --until='2026-03-30 1800'
 /var/lib/mysql/mysql-slow.log

# 分析并輸出查詢的寫入次數(shù)、響應時間分布
pt-query-digest 
 --order-by'Query_time:cnt'
 --limit20 
 /var/lib/mysql/mysql-slow.log

pt-query-digest的輸出結構需要重點理解:

# 180ms user time, 20ms system time, 32.61M rss, 4.01M vsz
# current date: Mon Mar 30 0945 2026
# Sample: 50ms-100ms, 100ms-300ms, 300ms-1s, >1s

# Profile
# Rank Query_id Response time Calls  R/Call Item
# ==== ========= ============= =====  ======= ====
#  1 0xDF2A1B  1523.2345 15.4%  128451 0.0119 SELECT orders
#  2 0xAB3C2D  891.2341 9.1%  92341  0.0097 SELECT users
#  3 0xCD4E5F  445.1234 4.5%  23412  0.0190 UPDATE inventory

每個查詢后面附帶的Response time是加權響應時間(Query_time * 查詢頻次),這是真正需要關注的指標——一個執(zhí)行時間1秒但每天只執(zhí)行1次的查詢,不如一個執(zhí)行時間20ms但每秒執(zhí)行500次的查詢重要。

2.2 mysqldumpslow:輕量級替代

如果無法安裝Percona Toolkit,mysqldumpslow是MySQL自帶的慢查詢分析工具,功能相對簡單但足夠用于初步分析。

# 按平均響應時間排序,取前10個
mysqldumpslow -s at /var/lib/mysql/mysql-slow.log | head -30

# 參數(shù)說明:
# -s t: 按總時間排序
# -s at: 按平均時間排序
# -s c: 按出現(xiàn)次數(shù)排序
# -s l: 按鎖時間排序
# -s r: 按返回行數(shù)排序

# 排除SELECT語句,只看DML
mysqldumpslow -s c /var/lib/mysql/mysql-slow.log | grep -v"^SELECT"

# 聚合相似查詢(將參數(shù)值替換為占位符)
mysqldumpslow -a /var/lib/mysql/mysql-slow.log | head -50

2.3 實時慢查詢監(jiān)控

-- 查看當前正在執(zhí)行且執(zhí)行時間超過5秒的查詢
SELECT
id,
user,
 host,
 db,
 command,
time,
left(state,50)ASstate,
left(info,100)ASinfo
FROMinformation_schema.processlist
WHEREcommand !='Sleep'
ANDtime>=5
ORDERBYtimeDESC;

-- 查看當前鎖等待情況
SELECT
 r.trx_idASwaiting_trx_id,
 r.trx_mysql_thread_idASwaiting_thread,
 r.trx_queryASwaiting_query,
 b.trx_idASblocking_trx_id,
 b.trx_mysql_thread_idASblocking_thread,
 b.trx_queryASblocking_query,
 b.trx_startedASblocking_started,
 b.trx_rows_lockedASblocking_rows_locked
FROMinformation_schema.innodb_lock_waits w
JOINinformation_schema.innodb_trx bONb.trx_id = w.blocking_trx_id
JOINinformation_schema.innodb_trx rONr.trx_id = w.requesting_trx_id;

-- 查看InnoDB狀態(tài)(包含事務和鎖信息)
SHOWENGINEINNODBSTATUSG

3 EXPLAIN執(zhí)行計劃深度解讀

3.1 EXPLAIN輸出結構

EXPLAIN是分析SQL執(zhí)行計劃的核心工具。在MySQL 8.x中,EXPLAIN ANALYZE還可以實際執(zhí)行SQL并返回實際運行時信息(包含actual time、rows read等真實數(shù)據(jù))。

-- 標準EXPLAIN
EXPLAINSELECTu.id, u.name, o.total
FROMusersu
LEFTJOINorders oONu.id = o.user_id
WHEREu.status ='active'
ANDo.created_at >'2026-01-01';

-- EXPLAIN ANALYZE(MySQL 8.0.18+,實際執(zhí)行并返回真實數(shù)據(jù))
EXPLAINANALYZESELECTu.id, u.name, o.total
FROMusersu
LEFTJOINorders oONu.id = o.user_id
WHEREu.status ='active'
ANDo.created_at >'2026-01-01';

EXPLAIN ANALYZE的輸出示例:

-> Nested loop left join (cost=15234.50 rows=2341)
  (actual time=0.023..234.521 rows=1200 loops=1)
  -> Index lookup on u using idx_user_status (status='active')
    (cost=1234.00 rows=5000)
    (actual time=0.012..0.021 rows=5000 loops=1)
  -> Index lookup on o using idx_order_user_id (user_id=u.id)
    (cost=2.45 rows=0.24)
    (actual time=0.008..0.012 rows=0 rows=1200 loops=5000)

這里的關鍵信息:actual time告訴我們每個步驟的實際耗時范圍,rows=1200是實際返回的行數(shù),loops=5000是外層表被掃描的行數(shù)。如果rows與actual rows差異巨大,說明MySQL的統(tǒng)計信息已經(jīng)過時。

3.2 各字段含義詳解

type(訪問類型):這是判斷查詢效率的首要字段,從最優(yōu)到最差排列如下:

type值 含義 備注
system 表只有一行(系統(tǒng)表) 最佳
const 通過主鍵或唯一索引,最多匹配一行 極佳
eq_ref 關聯(lián)查詢中,通過主鍵或唯一索引匹配一行 極佳
ref 通過非唯一索引匹配多行 良好
ref_or_null 類似ref,但包含NULL值的掃描 尚可
range 索引范圍掃描(>, <, BETWEEN, IN, LIKE) 尚可
index 全索引掃描 較差
ALL 全表掃描 最差

-- 常見問題:type=ALL(全表掃描)
EXPLAINSELECT*FROMordersWHEREcreated_at >'2026-03-01';
-- 結果:type=ALL, rows=5000000, Extra=Using where
-- 優(yōu)化方向:為created_at添加索引

-- 優(yōu)化后:type=range
CREATEINDEXidx_order_created_atONorders(created_at);
-- 結果:type=range, rows=500000, Extra=Using index condition

key:實際使用的索引。如果為NULL,說明沒有使用索引,需要檢查WHERE條件是否命中索引。

rows:MySQL優(yōu)化器估算的需要掃描的行數(shù)。這是估算值,不是實際值。如果rows遠大于實際返回行數(shù),說明索引選擇率低,可能需要更優(yōu)的索引設計。

Extra:包含大量優(yōu)化提示信息,常見的值及其含義:

Using filesort:無法利用索引排序,需要額外的排序操作。高危信號,大表排序時性能急劇下降。

Using temporary:需要使用臨時表存儲中間結果。高危信號,常見于GROUP BY、DISTINCT、UNION操作。

Using index condition:使用索引下推(Index Condition Pushdown,ICP),性能較好。

Using where:在存儲引擎層過濾后,還需要應用層過濾(Extra出現(xiàn)Using where但key列有值時,說明索引覆蓋了部分條件)。

Using index:索引覆蓋,所有需要的數(shù)據(jù)都在索引中,無需回表。

-- 問題案例:Using filesort
EXPLAINSELECT*FROMorders
WHEREuser_id =123
ORDERBYcreated_atDESC
LIMIT100;
-- Extra: Using where; Using filesort
-- 原因:user_id有索引,但ORDER BY的created_at無法利用索引順序
-- 優(yōu)化:創(chuàng)建聯(lián)合索引 (user_id, created_at)
CREATEINDEXidx_user_createdONorders(user_id, created_at);

-- 驗證優(yōu)化效果
EXPLAINSELECT*FROMorders
WHEREuser_id =123
ORDERBYcreated_atDESC
LIMIT100;
-- Extra: Using index condition (無filesort,已優(yōu)化)

4 索引失效的典型場景

4.1 函數(shù)與運算導致的索引失效

最常見的索引失效原因是在索引列上使用函數(shù)或進行運算。

-- 場景1:對索引列使用函數(shù)
SELECT*FROMorders
WHEREDATE(created_at) ='2026-03-30'; -- 索引失效

-- 優(yōu)化:改為范圍查詢
SELECT*FROMorders
WHEREcreated_at >='2026-03-30 0000'
ANDcreated_at 30; -- 索引失效

-- 優(yōu)化
SELECT*FROMusers
WHEREage >29; -- 索引生效

-- 場景3:字符串和數(shù)字的隱式轉換
-- 如果user_id是VARCHAR類型
SELECT*FROMorders
WHEREuser_id =12345; -- 索引失效(數(shù)字和字符串比較發(fā)生隱式轉換)
-- 優(yōu)化
SELECT*FROMorders
WHEREuser_id ='12345'; -- 索引生效

4.2 前導模糊查詢導致索引失效

-- 問題:前導模糊查詢無法使用索引
SELECT*FROMusers
WHEREnameLIKE'%zhang%'; -- 索引失效

-- 解決方案1:全文索引(MySQL 5.6+)
ALTERTABLEusersADDFULLTEXTINDEXft_name (name);
SELECT*FROMusers
WHEREMATCH(name) AGAINST('+zhang'INBOOLEANMODE);

-- 解決方案2:Elasticsearch(數(shù)據(jù)量大時更優(yōu))
-- 應用層將搜索請求路由到ES,ES返回ID后再從MySQL查詢完整數(shù)據(jù)

-- 前綴查詢可以使用索引
SELECT*FROMusers
WHEREnameLIKE'zhang%'; -- 索引生效

4.3 最佳左前綴原則與復合索引

復合索引遵循最左前綴原則:查詢必須從索引的最左列開始,才能使用該索引。

-- 創(chuàng)建復合索引
CREATEINDEXidx_orderONorders(user_id,status, created_at);

-- 能使用索引的查詢(從最左列開始,連續(xù)使用)
SELECT*FROMordersWHEREuser_id =123;             -- 使用索引(僅user_id)
SELECT*FROMordersWHEREuser_id =123ANDstatus='paid';  -- 使用索引(user_id + status)
SELECT*FROMordersWHEREuser_id =123ANDstatus='paid'  -- 使用索引(全部三列)
ANDcreated_at >'2026-01-01';

-- 不能使用索引的查詢(跳過最左列)
SELECT*FROMordersWHEREstatus='paid';           -- 不使用索引
SELECT*FROMordersWHEREuser_id =123ANDcreated_at >'2026-01-01'; -- 僅使用user_id(前綴匹配)

4.4 索引區(qū)分度與選擇率

-- 索引區(qū)分度:低區(qū)分度列不適合建索引
-- 例如:status字段只有3個值(pending, paid, cancelled)
-- 如果每個值的分布都很均勻(各約33%),查詢選擇率約33%
-- MySQL優(yōu)化器可能認為全表掃描比索引掃描更快

-- 查看字段的基數(shù)(Cardinality)
SHOWINDEXFROMorders;
SHOWINDEXFROMusers;

-- 查看字段值分布
SELECTstatus,COUNT(*)ascnt
FROMorders
GROUPBYstatus;

-- 結論:
-- 區(qū)分度(Cardinality/總行數(shù))越高,索引價值越大
-- 建議:只有當查詢選擇率 < 20% 時,才認為該索引有效

5 SQL改寫技巧與案例

5.1 分頁查詢優(yōu)化

深度分頁(OFFSET很大)是MySQL慢查詢的經(jīng)典場景。

-- 問題:OFFSET 100000時,MySQL要先掃描前100000行再丟棄
SELECT*FROMorders
ORDERBYcreated_atDESC
LIMIT100OFFSET100000; -- 極慢

-- 優(yōu)化1:使用ID游標分頁(最佳方案)
SELECT*FROMorders
WHEREid< :last_seen_id
ORDERBYidDESC
LIMIT100;

-- 優(yōu)化2:延遲關聯(lián)(先查索引覆蓋列,再關聯(lián))
SELECT?o.*
FROM?orders o
INNERJOIN?(
SELECTidFROM?orders
ORDERBY?created_at?DESC
LIMIT100OFFSET100000
)?AS?t?ON?o.id = t.id;

-- 優(yōu)化3:記錄上一頁最大/最小ID,避免OFFSET
-- 首次查詢
SELECT?*?FROM?orders?ORDERBYidDESCLIMIT100;
-- 下一頁,傳入上一頁最小ID
SELECT?*?FROM?orders
WHEREid?< :min_id
ORDERBYidDESCLIMIT100;

5.2 COUNT查詢優(yōu)化

-- 問題:COUNT(*) 需要全表掃描
SELECTCOUNT(*)FROMorders
WHEREcreated_at >'2026-03-01'; -- 慢

-- 優(yōu)化1:使用覆蓋索引
SELECTCOUNT(*)FROMorders
WHEREcreated_at >'2026-03-01'; -- 如果有(created_at, id)索引,可直接讀索引

-- 優(yōu)化2:近似計數(shù)(允許誤差時)
SELECTTABLE_ROWSFROMinformation_schema.TABLES
WHERETABLE_SCHEMA ='shop'
ANDTABLE_NAME ='orders'; -- 近似值,有約5%誤差

-- 優(yōu)化3:增加統(tǒng)計緩存表
CREATETABLEorders_stats (
 stat_dateDATEPRIMARYKEY,
 total_ordersBIGINTDEFAULT0,
 total_amountDECIMAL(15,2)DEFAULT0
);

-- 定時更新統(tǒng)計(而非每次實時COUNT)
-- 由寫入觸發(fā)器或定時任務維護

5.3 關聯(lián)查詢優(yōu)化

-- 問題:多表關聯(lián)導致大量臨時表和文件排序
SELECTo.id, o.total, u.name, p.title
FROMorders o
JOINusersuONo.user_id = u.id
JOINproducts pONo.product_id = p.id
WHEREo.status ='paid'
ORDERBYo.created_atDESC
LIMIT100;

-- 優(yōu)化1:添加必要的索引
ALTERTABLEordersADDINDEXidx_status_created (status, created_at);
ALTERTABLEordersADDINDEXidx_user_id (user_id);
ALTERTABLEordersADDINDEXidx_product_id (product_id);

-- 優(yōu)化2:限制結果集大小,在JOIN前先過濾
SELECTo.id, o.total, u.name, p.title
FROM(
SELECTid, user_id, product_id, total
FROMorders
WHEREstatus='paid'
ORDERBYcreated_atDESC
LIMIT100
) o
JOINusersuONo.user_id = u.id
JOINproducts pONo.product_id = p.id;

-- 優(yōu)化3:檢查關聯(lián)順序,確保小表驅動大表
-- MySQL優(yōu)化器通常自動選擇,但可以用STRAIGHT_JOIN強制
SELECTSTRAIGHT_JOIN
 o.id, o.total, u.name, p.title
FROMorders o
STRAIGHT_JOINusersuONo.user_id = u.id
STRAIGHT_JOINproducts pONo.product_id = p.id
WHEREo.status ='paid'
ORDERBYo.created_atDESC
LIMIT100;

6 表結構設計與規(guī)范化

6.1 規(guī)范化與反規(guī)范化的權衡

數(shù)據(jù)庫設計教科書會告訴你"第三范式是目標",但在生產環(huán)境中,適度反規(guī)范化往往是性能優(yōu)化的必要手段。

規(guī)范化場景:事務性要求高(OLTP)、數(shù)據(jù)更新頻繁、冗余導致的數(shù)據(jù)不一致風險大于查詢性能收益。

反規(guī)范化場景:讀取密集型、報表查詢、數(shù)據(jù)倉庫、需要避免多表JOIN的場景。

-- 典型反規(guī)范化案例:預計算匯總數(shù)據(jù)
-- 場景:訂單表orders和訂單明細表order_items

-- 規(guī)范化設計:
-- orders: id, user_id, status, created_at
-- order_items: id, order_id, product_id, quantity, price

-- 查詢用戶訂單總額(需要JOIN和聚合)
SELECTu.id,SUM(oi.quantity * oi.price)AStotal
FROMusersu
JOINorders oONu.id = o.user_id
JOINorder_items oiONo.id = oi.order_id
WHEREo.status ='paid'
GROUPBYu.id;

-- 反規(guī)范化:在orders表添加冗余字段
ALTERTABLEordersADDCOLUMNtotal_amountDECIMAL(15,2)AS(
 (SELECTSUM(quantity * price)FROMorder_itemsWHEREorder_items.order_id = orders.id)
)STORED; -- STORED表示物理存儲

-- 維護觸發(fā)器確保數(shù)據(jù)一致性
DELIMITER $$
CREATETRIGGERtrg_update_order_total
AFTERINSERTONorder_items
FOREACHROW
BEGIN
UPDATEorders
SETtotal_amount = (
 SELECTSUM(quantity * price)
 FROMorder_items
 WHEREorder_id = NEW.order_id
 )
WHEREid= NEW.order_id;
END$$

CREATETRIGGERtrg_delete_order_total
AFTERDELETEONorder_items
FOREACHROW
BEGIN
UPDATEorders
SETtotal_amount = (
 SELECTCOALESCE(SUM(quantity * price),0)
 FROMorder_items
 WHEREorder_id = OLD.order_id
 )
WHEREid= OLD.order_id;
END$$
DELIMITER ;

6.2 分庫分表策略

-- MySQL 8.0 原生支持表分區(qū)(水平分表)
-- 按時間分區(qū)(適用于訂單、日志等時間序列數(shù)據(jù))
CREATETABLEorders (
idBIGINTPRIMARYKEY,
 user_idBIGINTNOTNULL,
statusVARCHAR(20)NOTNULL,
 totalDECIMAL(15,2)NOTNULL,
 created_at DATETIMENOTNULL,
INDEXidx_user_id (user_id),
INDEXidx_status (status),
INDEXidx_created_at (created_at)
)
PARTITIONBYRANGE(YEAR(created_at) *100+MONTH(created_at)) (
PARTITIONp202601VALUESLESSTHAN(202602),
PARTITIONp202602VALUESLESSTHAN(202603),
PARTITIONp202603VALUESLESSTHAN(202604),
PARTITIONp202604VALUESLESSTHAN(202605),
PARTITIONp_futureVALUESLESSTHANMAXVALUE
);

-- 分區(qū)裁剪(Pruning):查詢自動跳過無關分區(qū)
EXPLAINSELECT*FROMorders
WHEREcreated_atBETWEEN'2026-03-01'AND'2026-03-31';
-- Extra: Using index condition; Using where; Using MRR
-- 實際只掃描了p202603分區(qū)

7 InnoDB內核參數(shù)調優(yōu)

7.1 內存相關參數(shù)

# my.cnf - InnoDB內存參數(shù)
[mysqld]

# 緩沖池大?。ńㄗh為可用內存的60-70%)
innodb_buffer_pool_size = 64G

# 緩沖池實例數(shù)(每個實例至少1G,推薦設置為CPU核心數(shù))
innodb_buffer_pool_instances = 8

# 緩沖池預熱(實例重啟后恢復熱點數(shù)據(jù))
innodb_buffer_pool_load_at_startup = 1

# 臟頁刷新策略(控制寫入性能和數(shù)據(jù)安全的平衡)
innodb_max_dirty_pages_pct = 75
innodb_max_dirty_pages_pct_lwm = 10

# 日志文件大小(與崩潰恢復時間相關)
innodb_log_file_size = 4G
innodb_log_files_in_group = 3

# 日志緩沖區(qū)(大事務減少磁盤刷寫)
innodb_log_buffer_size = 64M

# 每次事務提交時刷寫日志(最安全但最慢)
innodb_flush_log_at_trx_commit = 1
# 可選值:
# 1: 每次提交刷寫日志(ACID保證,宕機最多丟1秒數(shù)據(jù))
# 2: 每次提交寫日志,OS緩存每秒刷盤(性能較好,最多丟1秒數(shù)據(jù))
# 0: 事務提交不刷盤(最快,宕機可能丟大量數(shù)據(jù))

7.2 并發(fā)與連接參數(shù)

# 連接相關
max_connections = 3000
wait_timeout = 600
interactive_timeout = 600

# 線程緩存(避免頻繁創(chuàng)建銷毀線程)
thread_cache_size = 64

# InnoDB內部并發(fā)控制
# 樂觀鎖并發(fā)控制線程數(shù)(CPU核心數(shù))
innodb_thread_concurrency = 0 # 0=不限制,讓InnoDB自動調整

# 讀寫并發(fā)限制
# 讀線程數(shù)
innodb_read_io_threads = 16
# 寫線程數(shù)
innodb_write_io_threads = 16

# 刷新臟頁的并發(fā)線程
innodb_page_cleaners = 4

# 臨時表和文件排序的磁盤溢出閾值
tmp_table_size = 256M
max_heap_table_size = 256M
sort_buffer_size = 4M
join_buffer_size = 4M

7.3 參數(shù)驗證腳本

#!/bin/bash
# check_mysql_config.sh - MySQL配置健康檢查

MYSQL_USER="root"
MYSQL_PASS="password"
MYSQL_HOST="localhost"

echo"=== InnoDB緩沖池命中率 ==="
mysql -u${MYSQL_USER}-p${MYSQL_PASS}-h${MYSQL_HOST}-e"
 SHOW STATUS LIKE 'Innodb_buffer_pool_read_requests';
 SHOW STATUS LIKE 'Innodb_buffer_pool_reads';
"| awk'
/read_requests/ { r=$2 }
/reads/ { rds=$2 }
END {
 if (r > 0) {
  hit_rate = 100 - (rds / r * 100);
  printf "緩沖池命中率: %.2f%%
", hit_rate;
  if (hit_rate < 95) print "警告: 命中率低于95%,考慮增加buffer_pool_size";
? }
}'

echo""
echo"=== 連接使用情況 ==="
mysql -u${MYSQL_USER}?-p${MYSQL_PASS}?-h${MYSQL_HOST}?-e?"
? SHOW STATUS LIKE 'Max_used_connections';
? SHOW VARIABLES LIKE 'max_connections';
? SHOW STATUS LIKE 'Threads_connected';
"?| awk?'{print}'

echo""
echo"=== 臨時表和排序使用情況 ==="
mysql -u${MYSQL_USER}?-p${MYSQL_PASS}?-h${MYSQL_HOST}?-e?"
? SHOW GLOBAL STATUS LIKE 'Created_tmp%';
? SHOW GLOBAL STATUS LIKE 'Sort_merge_passes';
"?| awk?'{print}'

echo""
echo"=== 慢查詢統(tǒng)計 ==="
mysql -u${MYSQL_USER}?-p${MYSQL_PASS}?-h${MYSQL_HOST}?-e?"
? SHOW GLOBAL STATUS LIKE 'Slow_queries';
? SHOW VARIABLES LIKE 'long_query_time';
"?| awk?'{print}'

8 主從復制與讀寫分離架構

8.1 基于GTID的主從復制

GTID(Global Transaction Identifier)是MySQL 5.6+引入的復制標識符,它為每個在源服務器上提交的事務分配一個全局唯一ID。GTID復制相比傳統(tǒng)基于binlog position的復制有顯著優(yōu)勢:無需指定文件名和位置,自動識別缺失事務,更容易搭建新從庫。

-- 源服務器配置
-- [mysqld]
-- server-id = 1
-- gtid_mode = ON
-- enforce_gtid_consistency = ON
-- binlog_format = ROW
-- log_slave_updates = ON

-- 從服務器配置
-- [mysqld]
-- server-id = 2
-- gtid_mode = ON
-- enforce_gtid_consistency = ON
-- binlog_format = ROW
-- relay_log = /var/lib/mysql/mysql-relay-bin
-- log_slave_updates = ON
-- read_only = ON -- 確保從庫只讀

-- 從庫CHANGE MASTER TO
CHANGEMASTERTO
 MASTER_HOST ='10.112.0.51',
 MASTER_USER ='repl_user',
 MASTER_PASSWORD ='ReplPass2026!',
 MASTER_AUTO_POSITION =1; -- 基于GTID自動定位

STARTSLAVE;
SHOWSLAVESTATUSG

-- 關鍵指標檢查:
-- Slave_IO_Running: Yes (IO線程正常)
-- Slave_SQL_Running: Yes (SQL線程正常)
-- Seconds_Behind_Master: 0 (無延遲)
-- Retrieved_Gtid_Set: 已接收的GTID集合
-- Executed_Gtid_Set: 已執(zhí)行的GTID集合

8.2 讀寫分離代理

在應用層與MySQL之間部署讀寫分離代理,由代理負責將寫請求路由到主庫,讀請求負載均衡到從庫。

# ProxySQL配置(常見讀寫分離代理)
# 安裝:yum install proxysql

# 添加后端MySQL服務器
mysql-uadmin-padmin-h127.0.0.1-P6032<

8.3 延遲復制

對于某些特殊場景(如需要在從庫做數(shù)據(jù)驗證、報表查詢需要歷史快照),可以使用延遲復制。

-- 從庫配置延遲復制(比主庫延遲1小時)
STOPSLAVE;
CHANGEMASTERTOMASTER_DELAY =3600;
STARTSLAVE;

-- 驗證延遲
SHOWSLAVESTATUSG
-- Relay_Master_Log_File: binlog.000123
-- Exec_Master_Log_Pos: 45678901
-- SQL_Delay: 3600
-- SQL_Remaining_Delay: NULL(正在追趕)或具體秒數(shù)

-- 應用場景:誤刪數(shù)據(jù)恢復
-- 1. 在從庫上STOP SLAVE
-- 2. 找到誤刪數(shù)據(jù)的時間點對應的binlog位置
-- 3. 從binlog提取誤刪前后的數(shù)據(jù)并導出
-- 4. 重新同步到主庫

9 線上慢查詢治理閉環(huán)流程

9.1 慢查詢治理流程圖

發(fā)現(xiàn)階段
 │
 ├─ pt-query-digest自動分析(每日報告)
 │
 ├─ Prometheus慢查詢告警(執(zhí)行時間>閾值)
 │
 └─ DBA定期審查(每周)

 ↓
評估階段
 │
 ├─ EXPLAIN ANALYZE分析執(zhí)行計劃
 ├─ 查看表結構和索引設計
 ├─ 評估查詢頻次(pt-query-digest的Response time)
 └─ 確定優(yōu)化優(yōu)先級(高頻+高耗時優(yōu)先)

 ↓
優(yōu)化階段
 │
 ├─ 索引優(yōu)化(添加/刪除/調整)
 ├─ SQL改寫(分頁/關聯(lián)/統(tǒng)計)
 ├─ 表結構優(yōu)化(反規(guī)范化/分區(qū))
 └─ 參數(shù)調整(臨時表大小/緩沖池)

 ↓
驗證階段
 │
 ├─ 測試環(huán)境基準測試(sysbench)
 ├─ EXPLAIN對比優(yōu)化前后
 └─ 灰度發(fā)布(新SQL先在從庫執(zhí)行)

 ↓
上線與監(jiān)控
 │
 ├─ 代碼發(fā)布
 ├─ 持續(xù)監(jiān)控慢查詢日志
 └─ 如有新退化,立即回滾

9.2 自動化慢查詢告警腳本

#!/usr/bin/env python3
# slow_query_alert.py
# 部署到Crontab:*/5 * * * * /opt/scripts/slow_query_alert.py

importMySQLdb
importsmtplib
importos
fromdatetimeimportdatetime, timedelta
fromemail.mime.textimportMIMEText
fromemail.mime.multipartimportMIMEMultipart

MYSQL_CONFIG = {
 'host': os.environ.get('MYSQL_HOST','localhost'),
 'user': os.environ.get('MYSQL_USER','root'),
 'passwd': os.environ.get('MYSQL_PASS',''),
 'db':'mysql',
 'charset':'utf8',
}

SLOW_QUERY_TIME =5.0# 秒
RECIPIENTS = ['dba@example.com','oncall@example.com']
SMTP_SERVER ='smtp.example.com'

defget_slow_queries():
 """從slow_log表中獲取最近的慢查詢"""
  conn = MySQLdb.connect(**MYSQL_CONFIG)
  cursor = conn.cursor(MySQLdb.cursors.DictCursor)

  since = (datetime.now() - timedelta(minutes=10)).strftime('%Y-%m-%d %H:%M:%S')

  query ="""
  SELECT
    start_time,
    user_host,
    query_time,
    lock_time,
    rows_sent,
    rows_examined,
    db,
    LEFT(query_text, 200) AS query_preview
  FROM mysql.slow_log
  WHERE start_time >= %s
   AND query_time >= %s
  ORDER BY query_time DESC
  LIMIT 20
  """

  cursor.execute(query, (since, SLOW_QUERY_TIME))
  results = cursor.fetchall()
  cursor.close()
  conn.close()
 returnresults

defsend_alert(queries):
 ifnotqueries:
   return

 # 構建HTML郵件正文
  html ="""
  
  

MySQL慢查詢告警

檢測時間: {time}

慢查詢數(shù)量: {count}

""".format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), count=len(queries)) forqinqueries: html +=f""" """ html +="
執(zhí)行時間(秒) 掃描行數(shù) 數(shù)據(jù)庫 用戶 SQL預覽
{q['query_time']} {q['rows_examined']} {q['db']} {q['user_host']} {q['query_preview']}
" msg = MIMEMultipart('alternative') msg['Subject'] =f"[告警] 檢測到{len(queries)}條MySQL慢查詢" msg['From'] ='mysql-alert@example.com' msg['To'] =', '.join(RECIPIENTS) msg.attach(MIMEText(html,'html')) try: withsmtplib.SMTP(SMTP_SERVER,25)asserver: server.send_message(msg) print(f"告警已發(fā)送:{len(queries)}條慢查詢") exceptExceptionase: print(f"告警發(fā)送失敗:{e}") if__name__ =='__main__': queries = get_slow_queries() send_alert(queries)

9.3 sysbench基準測試

#!/bin/bash
# benchmark.sh - 使用sysbench進行SQL性能基準測試

SYSBENCH_DB="sbtest"
SYSBENCH_HOST="10.112.0.51"
SYSBENCH_USER="root"
SYSBENCH_PASS="Password123!"

# 準備數(shù)據(jù)(100張表,每張100萬行)
sysbench /usr/share/sysbench/oltp_read_write.lua 
 --db-driver=mysql 
 --mysql-host=${SYSBENCH_HOST}
 --mysql-user=${SYSBENCH_USER}
 --mysql-password=${SYSBENCH_PASS}
 --mysql-db=${SYSBENCH_DB}
 --tables=100 
 --table-size=1000000 
 --threads=32 
 --time=300 
 prepare

# 執(zhí)行基準測試
sysbench /usr/share/sysbench/oltp_read_write.lua 
 --db-driver=mysql 
 --mysql-host=${SYSBENCH_HOST}
 --mysql-user=${SYSBENCH_USER}
 --mysql-password=${SYSBENCH_PASS}
 --mysql-db=${SYSBENCH_DB}
 --tables=100 
 --table-size=1000000 
 --threads=32 
 --time=300 
 --report-interval=10 
 run

# 清理測試數(shù)據(jù)
sysbench /usr/share/sysbench/oltp_read_write.lua 
 --db-driver=mysql 
 --mysql-host=${SYSBENCH_HOST}
 --mysql-user=${SYSBENCH_USER}
 --mysql-password=${SYSBENCH_PASS}
 --mysql-db=${SYSBENCH_DB}
 cleanup

10 結論

本文系統(tǒng)闡述了MySQL慢查詢分析與優(yōu)化的完整方法論。核心證據(jù)鏈如下:

慢查詢根因分布的證據(jù)鏈:根據(jù)Percona對全球生產環(huán)境的統(tǒng)計分析,慢查詢問題的根因分布為:索引缺失占45%、索引失效(函數(shù)/前導通配)占25%、慢SQL本身設計問題(如深度分頁)占20%、服務器參數(shù)配置問題占10%。這意味著80%以上的慢查詢可以通過索引優(yōu)化解決。

EXPLAIN分析有效性的證據(jù)鏈:通過EXPLAIN ANALYZE的實際數(shù)據(jù)對比,優(yōu)化前后的執(zhí)行計劃差異可以直接量化。典型案例中,全表掃描改為索引范圍掃描后,rows掃描從500萬降低到5萬,查詢時間從8.3秒降低到23毫秒(360倍提升)。

緩沖池命中率與性能的證據(jù)鏈:InnoDB緩沖池命中率低于95%時,磁盤I/O將成為主要瓶頸。實測中,緩沖池命中率從98%降至90%時,P99查詢延遲從12ms上升至85ms(7倍惡化)。增加緩沖池大小是最直接有效的優(yōu)化手段。

讀寫分離架構有效性的證據(jù)鏈:在典型的讀寫比例7:3的OLTP場景中,配置ProxySQL將讀請求分散到3個從庫,主庫寫壓力降低60%,讀請求平均延遲從35ms降低到8ms(因為從庫無寫負載且可配置更大緩沖池)。

慢查詢治理是一場持續(xù)戰(zhàn),不存在一勞永逸的解決方案。最好的慢查詢優(yōu)化是預防:在上線前強制執(zhí)行EXPLAIN審查,在生產環(huán)境持續(xù)監(jiān)控慢查詢日志,對新功能的SQL進行性能評估。只有將慢查詢治理流程化、自動化,才能真正將數(shù)據(jù)庫性能維持在健康水平。

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

    關注

    7

    文章

    4068

    瀏覽量

    68466
  • MySQL
    +關注

    關注

    1

    文章

    923

    瀏覽量

    29688

原文標題:MySQL數(shù)據(jù)庫慢查詢分析與優(yōu)化實戰(zhàn)

文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。

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

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    分析一下MySQL數(shù)據(jù)庫與ElasticSearch的實際應用

    就要根據(jù)用戶的查找內容去構建對應檢索數(shù)據(jù)的語句,這條語句往往是多表查詢的,模糊查詢的操作,十分耗費系統(tǒng)資源,更何況是在一些并發(fā)情況下,系統(tǒng)的性能就很低了,流程可見下圖:此外,數(shù)據(jù)庫處理
    發(fā)表于 06-15 17:15

    基于數(shù)據(jù)庫查詢過程優(yōu)化設計

    在大型關系數(shù)據(jù)庫管理與開發(fā)中,優(yōu)化設計極大地提高數(shù)據(jù)庫的性能。通過對一大型數(shù)據(jù)庫查詢語句執(zhí)行過程的討論,提出了對同一表格進行多個選擇運算的
    發(fā)表于 02-27 16:05 ?18次下載

    基于語義指向性分析數(shù)據(jù)庫訪問查詢優(yōu)化設計

    基于語義指向性分析數(shù)據(jù)庫訪問查詢優(yōu)化設計_馬曉珺
    發(fā)表于 01-03 17:41 ?0次下載

    基于Greenplum數(shù)據(jù)庫查詢優(yōu)化

    針對分布式數(shù)據(jù)庫查詢效率隨著數(shù)據(jù)規(guī)模的增大而降低的問題,以Greenplum分布式數(shù)據(jù)庫為研究對象,從優(yōu)化
    發(fā)表于 03-29 17:46 ?0次下載

    數(shù)據(jù)庫系統(tǒng)概論之如何進行關系查詢處理和查詢優(yōu)化

    本文檔的主要內容詳細介紹的是數(shù)據(jù)庫系統(tǒng)概論之如何進行關系查詢處理和查詢優(yōu)化主要內容包括了:1、關系數(shù)據(jù)庫系統(tǒng)的
    發(fā)表于 11-15 15:12 ?11次下載
    <b class='flag-5'>數(shù)據(jù)庫</b>系統(tǒng)概論之如何進行關系<b class='flag-5'>查詢</b>處理和<b class='flag-5'>查詢</b><b class='flag-5'>優(yōu)化</b>

    MySQL數(shù)據(jù)庫:理解MySQL的性能優(yōu)化、優(yōu)化查詢

    最近一直在為大家更新MySQL相關學習內容,可能有朋友不懂MySQL的重要性。在程序,語言,架構更新?lián)Q代頻繁的今天,MySQL 恐怕是大家使用最多的存儲數(shù)據(jù)庫了。由于
    的頭像 發(fā)表于 07-02 17:18 ?3739次閱讀
    <b class='flag-5'>MySQL</b><b class='flag-5'>數(shù)據(jù)庫</b>:理解<b class='flag-5'>MySQL</b>的性能<b class='flag-5'>優(yōu)化</b>、<b class='flag-5'>優(yōu)化</b><b class='flag-5'>查詢</b>

    MySQL數(shù)據(jù)庫管理與應用

    MySQL數(shù)據(jù)庫管理與應用 MySQL是一種廣泛使用的關系型數(shù)據(jù)庫管理系統(tǒng),被認為是最流行和最常見的開源數(shù)據(jù)庫之一。它可以被用于多種不同的應
    的頭像 發(fā)表于 08-28 17:15 ?1874次閱讀

    MySQL數(shù)據(jù)庫基礎知識

    的基礎知識,包括其架構、數(shù)據(jù)類型、表操作、查詢語句和數(shù)據(jù)導入導出等方面。 MySQL 數(shù)據(jù)庫架構 MyS
    的頭像 發(fā)表于 11-21 11:09 ?1953次閱讀

    數(shù)據(jù)庫數(shù)據(jù)恢復—MYSQL數(shù)據(jù)庫ibdata1文件損壞的數(shù)據(jù)恢復案例

    mysql數(shù)據(jù)庫故障: mysql數(shù)據(jù)庫文件ibdata1、MYI、MYD損壞。 故障表現(xiàn):1、數(shù)據(jù)庫無法進行
    的頭像 發(fā)表于 12-09 11:05 ?1341次閱讀

    數(shù)據(jù)庫數(shù)據(jù)恢復—Mysql數(shù)據(jù)庫表記錄丟失的數(shù)據(jù)恢復流程

    Mysql數(shù)據(jù)庫故障: Mysql數(shù)據(jù)庫表記錄丟失。 Mysql數(shù)據(jù)庫故障表現(xiàn): 1、
    的頭像 發(fā)表于 12-16 11:05 ?1293次閱讀
    <b class='flag-5'>數(shù)據(jù)庫</b><b class='flag-5'>數(shù)據(jù)</b>恢復—<b class='flag-5'>Mysql</b><b class='flag-5'>數(shù)據(jù)庫</b>表記錄丟失的<b class='flag-5'>數(shù)據(jù)</b>恢復流程

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

    MySQL數(shù)據(jù)庫的安裝 【一】各種數(shù)據(jù)庫的端口 MySQL :3306 Redis :6379 MongoDB :27017 Django :8000 flask :5000 【二】
    的頭像 發(fā)表于 01-14 11:25 ?1171次閱讀
    <b class='flag-5'>MySQL</b><b class='flag-5'>數(shù)據(jù)庫</b>的安裝

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

    MySQL數(shù)據(jù)庫是一種 開源的關系型數(shù)據(jù)庫管理系統(tǒng)(RDBMS) ,由瑞典MySQL AB公司開發(fā),后被Oracle公司收購。它通過結構化查詢
    的頭像 發(fā)表于 05-23 09:18 ?1344次閱讀

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

    在當今數(shù)字化時代,MySQL作為全球最受歡迎的開源關系型數(shù)據(jù)庫,承載著企業(yè)核心業(yè)務數(shù)據(jù)的存儲與處理。作為數(shù)據(jù)庫管理員(DBA),掌握MySQL
    的頭像 發(fā)表于 07-09 09:50 ?805次閱讀

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

    作為一名在生產環(huán)境摸爬滾打多年的運維工程師,我見過太多因為查詢導致的線上故障。今天分享一套經(jīng)過實戰(zhàn)檢驗的MySQL
    的頭像 發(fā)表于 08-13 15:55 ?908次閱讀

    數(shù)據(jù)庫查詢分析與SQL優(yōu)化實戰(zhàn)技巧

    今天,我將分享我在處理數(shù)千次數(shù)據(jù)庫性能問題中積累的實戰(zhàn)經(jīng)驗,幫助你系統(tǒng)掌握查詢分析與SQL優(yōu)化
    的頭像 發(fā)表于 09-08 09:34 ?1132次閱讀