SQL優化簡單思路

1. 背景

在實際生產中,因為SQL較慢、SQL關聯不合理、不了解索引的性質、不熟悉mysql執行計劃分析,可能會出現一些生產事故,本文會簡單說明SQL通常的優化分析思路。
基本的優化原則:

  1. 先優化SQL
  2. 再優化mysql server
  3. 最后優化硬件

2. 優化sql從執行計劃開始

執行計劃是mysql語句優化中最長使用的方式。相比于開發人員依賴經驗、感覺進行優化,explain將會提供精確的分析結論。
其使用非常簡單:

explain select data from A where id = "asdaqsdqw"

explain后面加上你想要分析的語句,執行即可。
重點在于執行計劃結果的分析。
來看一個典型的explain結果:
explain典型結果

2.1 table字段

2.1.1 分析

涉及到的表。一般而言,只涉及一張表就只有一行,但涉及多張表如n張表時,則至少有n行,因為多表join的過程可能出現中間臨時表,這也是表,雖然不會出現在sql中,但執行時是會計算在執行計劃內的。
對于inner join情況,先出現的表是驅動表,后出現的表是被驅動表。

2.1.1.1 驅動表

驅動表(Driving Table) 是作為外層循環的表,被驅動表(Driven Table)是作為內層循環的表。選擇合適的驅動表能顯著減少連接的總計算量,核心原則是:“小表驅動大表”(用數據量更小的表作為驅動表)

理由:
總操作次數 ≈ 驅動表行數 × 被驅動表中每行的匹配次數。
若驅動表是小表(如 100 行),被驅動表是大表(如 100 萬行):總操作次數約為 100 × 平均匹配次數。
若反過來(大表驅動小表):總操作次數約為 100萬 × 平均匹配次數,是前者的 1 萬倍。
顯然,小表作為驅動表能大幅減少外層循環次數,降低總計算開銷。
偽代碼輔助理解:

#偽代碼:嵌套循環連接
for 驅動表中的每一行 a in A:for 被驅動表中匹配 a 的行 b in B:輸出 (a, b)

所以,explain的結果,行數越少越好,用于驅動的表規模越小越好。
注意,這里的 “小” 不是指表的原始大小,而是 “經過 WHERE 過濾后參與連接的行數”,比如A表原本有100萬數據,但謂詞下推過濾后剩30條,那么驅動規模按30計算。

此外,被驅動表中,即使一個字段被加了索引,也很有可能在查詢中不生效。
比如:
A表內連接B表,在where子句中使用B表的create_time作為時間范圍限制,B.create_time已經加了索引,但為什么這個索引沒生效?
因為MySQL 會先掃描 A表 的行,再逐行到 B表 中匹配連接條件(如 A.id = B.a_id)。此時,WHERE B.create_time 的過濾條件可能在 連接之后 執行(即先匹配所有滿足連接條件的行,再過濾時間范圍),導致索引失效,退化為普通全集掃描。
這個要點在后面線上事故分析時會看到實際案例。

2.1.2 使用

多數數據庫(如 MySQL、PostgreSQL、SQL Server)的優化器會根據表的統計信息(行數、索引分布等)自動選擇 “成本最低” 的驅動表,無需人工干預。
但在以下場景可能需要手動指定:

  • 統計信息過時(如剛批量插入數據未更新統計信息),導致優化器誤判表大小(可以執行ANALYZE TABLE Table_name來解決);
  • 復雜多表連接(3 張以上),優化器可能因計算復雜度選擇次優順序。
    手動指定方式(以 MySQL 為例):
    用 STRAIGHT_JOIN 強制左表為驅動表:
-- 強制 A 作為驅動表,B 作為被驅動表
SELECT * FROM A STRAIGHT_JOIN B ON A.id = B.a_id;

2.2 type字段(重要)

type 字段 描述了 表的訪問方式(即 MySQL 如何查找表中的行),是判斷查詢效率的核心指標之一。type 的值從 “性能最優” 到 “性能最差” 有明確的優先級,直接反映了查詢對索引的利用效率
type 可能出現的值及含義(按性能從優到劣排序)

  1. system:表中只有一行數據(系統表),是 const 類型的特例,幾乎不會在生產中出現
  2. const:通過主鍵或唯一索引的 等值查詢 匹配到一行數據(最多一行),唯一主鍵且不join時會有
  3. eq_ref:在 多表 JOIN 中,被驅動表的連接列是 主鍵或唯一非空索引,且每個驅動表的行只能匹配到被驅動表的一行
  4. unique_subquery:子查詢中使用了唯一索引,替代了 eq_ref 的 JOIN 方式(適用于 IN 子查詢),一般認為和eq_ref效率基本一致
  5. ref:非唯一索引的等值查詢,或 JOIN 中被驅動表的連接列是 非唯一索引,可能匹配到多行
  6. ref_or_null:類似 ref,但額外包含對 NULL 值的查詢(即條件中包含 IS NULL)
  7. index_merge:MySQL 使用 索引合并優化,即同時使用多個單列索引,將結果合并(交集 / 并集)后返回
  8. range:使用索引進行 范圍查詢,只檢索索引中某一范圍內的行,范圍越小(匹配行數越少)效率越高。若范圍過大(接近全表),可能退化為全表掃描
  9. index:掃描 整個索引樹(而非全表),但未利用索引篩選,僅通過索引覆蓋數據(即 “索引全掃描”)
  10. ALL:全表掃描(Full Table Scan),MySQL 會遍歷整個表的所有行來查找匹配的記錄,最差的情況

目標是至少確保 type 達到 range 級別,最好能達到 ref 或更高。若出現 ALL 或 index,需檢查是否缺少索引、索引失效或查詢條件不合理,及時優化。

2.3 possible_keys字段

mysql執行計劃預先判斷某一次查詢可能涉及的索引,如果你期望命中的索引沒有命中,可以先在這里看你的索引是否被排除在預期之外,并據此優化。
有時mysql的執行計劃如果只看keys字段,看起來好像有索引使用,但其實如果把possible_keys和keys連起來看,那么就會發現possible_keys為null時,即使keys有值也不一定走索引。

2.4 key字段(重要)

關鍵字段,代表實際命中的索引。如果為null則代表沒有命中索引。要結合possible_keys字段一起分析。

2.5 ref字段

ref 字段用于描述:在使用索引查找匹配行時,哪些 “值”(可能是列名、常量或函數結果)被用來與索引列進行比較。它直觀地展示了 MySQL 如何利用索引來定位符合條件的行,是理解索引匹配邏輯的重要依據

  1. 取值為 常量(如 const、具體值),當前表的索引列與常量進行等值匹配 時,ref 會顯示為 const 或具體的常量值,說明用 “常量” 與索引列比對
  2. 取值為 其他表的列名:在 多表 JOIN 場景中,當被驅動表的索引列與驅動表的某列進行匹配時,ref 會顯示驅動表的 “表名。列名”,說明用驅動表的列值與被驅動表的索引列比對。
  3. 取值為 函數或表達式結果:當查詢條件中,索引列與函數 / 表達式的結果比對時,ref 會顯示函數或表達式的相關信息(不同 MySQL 版本可能顯示方式不同)
  4. 取值為 NULL:特殊情況下(如 type 為 index 或 ALL 時),ref 可能為 NULL,表示沒有使用 “具體值” 與索引列比對(可能是全索引掃描或全表掃描,無需比對值)
    總而言之,這個字段是key字段的延展,用來告訴你你的索引和什么進行比對

2.6 rows字段

rows 表示 MySQL 優化器估計需要掃描的行數(即 “預計要檢查多少行才能找到符合條件的記錄”),rows 值越小,說明查詢需要檢查的行數越少,效率通常越高

2.7 filtered字段

filtered 表示經過表級條件過濾后,剩余行所占的百分比(取值范圍 0~100),filtered = (滿足條件的行數 / 掃描的行數) * 100

2.8 Extra字段(重要)

Extra 字段用于提供查詢執行計劃的額外細節信息,補充 type、ref 等字段未涵蓋的執行邏輯(如索引使用細節、排序方式、臨時表使用等)。它是判斷查詢是否需要優化的關鍵依據,許多 Extra 值直接反映了查詢的性能瓶頸(如是否需要臨時表、是否需要額外排序等)。

  1. Using index:使用了覆蓋索引(Covering Index),即查詢所需的所有列(SELECT 后的列 + WHERE/JOIN 條件的列)都包含在索引中,無需回表訪問主鍵索引獲取數據,性能極佳,是索引優化的理想狀態,避免了回表的 IO 開銷
  2. Using where:MySQL 使用了 WHERE 條件過濾數據,但未使用索引(或雖使用索引但索引未覆蓋所有過濾條件,需在服務器層進一步過濾),性能:若 type 為 ALL(全表掃描),則性能較差,需添加索引;若 type 為 range 等,需結合具體場景優化
  3. Using index condition:啟用了索引條件下推(Index Condition Pushdown, ICP) 優化,將部分 WHERE 條件下推到存儲引擎層,在索引掃描時直接過濾不符合條件的行,減少回表次數
  4. Using temporary:MySQL 需要創建臨時表來存儲中間結果(如用于 GROUP BY、DISTINCT 或 JOIN 時的排序 / 去重),性能較差,臨時表(尤其是磁盤臨時表)會增加 IO 和內存開銷,數據量大時可能導致嚴重性能問題。必須優化(如為 GROUP BY 列添加索引)
  5. Using filesort:MySQL 需要對結果進行額外排序(無法利用索引的自然順序完成排序),排序操作可能在內存或磁盤中進行(數據量大時會寫入臨時文件),性能較差,排序是 CPU 密集型操作,數據量大時會嚴重影響性能。需優化(如為 ORDER BY 列添加索引,或調整排序順序與索引一致)
  6. Using filesort with small result:類似 Using filesort,但排序的結果集很小,排序開銷較低(MySQL 認為無需優化)
  7. Using join buffer (Block Nested Loop) 或 Using join buffer (Batched Key Access):JOIN 操作中,被驅動表無可用索引,MySQL 使用連接緩沖區(Join Buffer)存儲驅動表的數據,再與被驅動表的行逐行比對(嵌套循環連接),性能極差,無索引的 JOIN 是性能殺手,數據量大時需為連接列添加索引
  8. Range checked for each record (index map: N):MySQL 無法確定哪個索引更高效,因此對驅動表的每一行,都會檢查被驅動表的多個索引(index map: N 表示候選索引的位圖),選擇當前行最匹配的索引。可以嘗試手動執行此語句解決:ANALYZE TABLE
  9. Impossible WHERE:WHERE 條件永遠為 FALSE,MySQL 無需掃描表(如 WHERE 1=0),性能極高但毫無意義
  10. No tables used:不涉及表查詢,比如select 1+1

總結而言:

  • Using temporary:避免臨時表,通過索引優化 GROUP BY/DISTINCT;
  • Using filesort:添加排序索引,或調整 ORDER BY 順序與索引一致;
  • Using join buffer:為 JOIN 的 ON 條件列添加索引;
  • Using where 且 type=ALL:為過濾列添加索引,避免全表掃描。

3. Mysql索引

特別地,任何索引在經過函數計算后都會失效。

3.1 主鍵索引

唯一標識表中的記錄,不允許重復,且不允許為 NULL,每個表只能有一個主鍵索引。
InnoDB 中,主鍵索引是聚簇索引(葉子節點直接存儲整行數據),性能最優。
執行計劃:

{
"explain\r\nselect * from A where id = 1944811872387059713": [{"id" : 1,"select_type" : "SIMPLE","table" : "A","partitions" : null,"type" : "const","possible_keys" : "PRIMARY","key" : "PRIMARY","key_len" : "8","ref" : "const","rows" : 1,"filtered" : 100.0,"Extra" : null}
]}

3.2 唯一索引

確保索引列的值唯一(允許 NULL,但 NULL 只能出現一次)。Mysql非主鍵索引中最高效的鍵,它在建表時表達為UNIQUE,可以唯一定位一條數據。
對于唯一鍵,建議的使用方式只有一種,就是等于。
舉例:
A表的唯一鍵是id,那么查詢時應當:
select data from A where id = xxx
其執行計劃表達為:

{
"explain\r\nselect * from service_entity where service_address=\"127.0.0.1\"": [{"id" : 1,"select_type" : "SIMPLE","table" : "service_entity","partitions" : null,"type" : "const","possible_keys" : "UKjety3jc210qsbnuij9yl61nf2","key" : "UKjety3jc210qsbnuij9yl61nf2","key_len" : "1022","ref" : "const","rows" : 1,"filtered" : 100.0,"Extra" : null}
]}

特別地,如果此唯一索引為int或bigint,且符合嚴格遞增,那么我們遇到深分頁問題時,可以嘗試用唯一索引的范圍來優化查詢,這同樣也適用在主鍵索引為數值的情況:

select id from A where id >= xxx and id < yyy and ...

其執行計劃表達為:

{
"explain\r\nselect * from test_a where connections > 1 and connections <= 3\r\n\r\n\r\n": [{"id" : 1,"select_type" : "SIMPLE","table" : "test_a","partitions" : null,"type" : "range","possible_keys" : "idx_conn","key" : "idx_conn","key_len" : "5","ref" : null,"rows" : 2,"filtered" : 100.0,"Extra" : "Using where; Using index"}]
}

此處我們可以看到Extra中仍然存在using where,這是因為除了范圍沒有使用別的索引,如果查詢還有其他索引,則還有優化空間

3.3 時間索引

時間索引一般而言使用模式比較固定,例如create_time,大多數場合都是create_time > xxx and create_time < yyy的使用模式。
不過也有一些細節可以注意。
看下面的SQL

select data from A where create_time >= '2025-09-01 00:00:00' and create_time < '2025-09-02 23:59:59'

這是我們在實際生產中見到的一類寫法,這一類寫法沒有什么大問題,在必須精確到毫秒數的場合一定是這種寫法。
其執行計劃表現為:

{
"explain\r\n select * from A where create_time >= '2025-09-01 00:00:00' and create_time < '2025-09-02 23:59:59'": [{"id" : 1,"select_type" : "SIMPLE","table" : "A","partitions" : null,"type" : "range","possible_keys" : "timer_cre","key" : "timer_cre","key_len" : "4","ref" : null,"rows" : 45534,"filtered" : 100.0,"Extra" : "Using index condition; Using MRR"}
]}

特別地,Extra 列出現 “Using MRR” ,表示優化器啟用了 MRR(Multi-Range Read,多范圍讀取)優化策略。這是 MySQL 針對 “二級索引查詢 + 回表” 場景設計的性能優化手段,核心目標是將 “隨機 IO” 轉化為 “順序 IO” ,減少磁盤 IO 開銷,提升查詢效率。總之,是一種比using where更優的表現。

如果是限定時間范圍查詢,比如某些場合已經預先確定一次查詢必然查且只查一個自然日,那么更好的寫法是:

select data from A where create_time = '2025-09-02'

其執行計劃表現為:

{
"explain\r\nselect * from async_main_task_0 where create_time = \"2025-09-09\"\r\n\r\n\r\n": [{"id" : 1,"select_type" : "SIMPLE","table" : "async_main_task_0","partitions" : null,"type" : "ref","possible_keys" : "timer_cre","key" : "timer_cre","key_len" : "4","ref" : "const","rows" : 1,"filtered" : 100.0,"Extra" : null}
]}

3.4 字符串索引

這是實際生產中最常見到的一類索引,這一類索引不像數字索引那樣可以簡單的比較大小,其效率極大地取決于用戶的使用方式。

3.4.1 最高效的方式

當然是直接等于:

select data from A where trace_id = "asdqwdzscqwfcqf"

其執行計劃表現為:

{
"explain\r\n\r\nselect * from A where task_id = \"47f8fc36febb44f8afb5d08240bf4868\"": [{"id" : 1,"select_type" : "SIMPLE","table" : "A","partitions" : null,"type" : "ref","possible_keys" : "idx_task_id","key" : "idx_task_id","key_len" : "1022","ref" : "const","rows" : 1,"filtered" : 100.0,"Extra" : null}
]}

3.4.2 次優的方式

左like:

select data from A where trace_id like "asdwd%"

其執行計劃表現為:

{
"explain\r\n\r\nselect * from A where task_id like \"47f8fc36febb44f8afb5d08240bf4%\"": [{"id" : 1,"select_type" : "SIMPLE","table" : "A","partitions" : null,"type" : "range","possible_keys" : "idx_task_id","key" : "idx_task_id","key_len" : "1022","ref" : null,"rows" : 1,"filtered" : 100.0,"Extra" : "Using index condition"}
]}

3.4.3 不生效的方式

  1. 兩側like: select data from A where trace_id like “%asdqwd%”
  2. 對列做函數計算:select data from A where len(trace_id) > 100
    以上兩種,絕對不要在大數據量、大QPS場合出現必死。

3.5 聯合索引

3.5.1 聯合索引的建立

聯合索引涉及到多個字段,由于mysql基礎數據結構的限定,必須要在建立索引時就決定好從左到右的順序。
以A表為例,如果其包含字段x,y,z,j,k,l,當我們想使用聯合索引時,就必須預先分析下面兩個問題:

  1. 要用哪些字段加入到聯合索引
  2. 這些索引的排序
    這兩個問題極其重要,會極大影響索引的過濾效果。
    下面給出一些分析標準來確定哪些字段應當加入聯合索引:
  • 區分度極高(如各種ID),且固定出現在where條件里
  • 可以大規模過濾掉非目標數據(如時間段,如各種數值區間)
  • 在條件查詢的where中高頻出現
  • 絕不參與任何函數計算

假如根據上面的標準,我們篩選出x,z,j三個作為聯合索引,那么排序應當按照如下原則從左到右排列:

  • 固定出現的查詢條件
  • 高頻出現的查詢條件
  • 可以過濾更多數據的查詢條件
  • 未必會出現的查詢條件

比如,z條件必定出現在每次查詢中,則z排第一位;x大概率出現,且為id字段,則排第2位;j不長出現,則排第三位,那么創建索引的語句為:

create index `udx_z_x_j` on A (z,x,j)

一定要注意,越靠左的索引越重要,使用聯合索引查詢時,左邊的索引一旦缺失,整個查詢效率會有巨幅下降。
創建該索引后,只要z,x出現,那么j不出現,也至少可以走兩個索引。

3.5.2 聯合索引的查詢

查詢過程務必遵守創建聯合索引時的左右順序,盡最大可能保證建立索引時左邊的索引在where中出現:

select data from A where z="asdqdqw" and x like "niqdwnu%"

如果沒有遵循這個原則,那么從左側哪一個查詢條件開始缺失,就會從哪一個索引開始失效。例如:

select data from A where x = 'aedqw'

看起來好像使用了聯合索引中的x,但由于最左的z缺失,所以整個聯合索引都失效了。
實際SQL:

-- test_jpa.test_union_index definitionCREATE TABLE `test_union_index` (`id` bigint(20) NOT NULL,`task_type` varchar(128) NOT NULL COMMENT '任務分類:智批改任務,劃題任務,直接干預任務等',`task_status` varchar(32) NOT NULL COMMENT '任務狀態,INIT:初始化,DOING:進行中,FAILED:失敗,SUCCEED:成功',`task_stage` varchar(32) NOT NULL COMMENT '任務階段,LAYOUT_ANALYSIS:版面分析,GET_TOPIC:取題,RECOGNIZATION:識別,CORRECTION:批改,INTERENTION:干預',PRIMARY KEY (`id`),KEY `udx_type_status_stage` (`task_type`,`task_status`,`task_stage`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;explain
select * from test_union_index
where task_status="STATUS_092" andtask_stage = "STAGE_092"

實際的執行計劃為:

{
"explain\r\nselect\r\n\t*\r\nfrom\r\n\ttest_union_index\r\nwhere\r\n\t\r\n\ttask_status=\"STATUS_092\" and\r\n\ttask_stage = \"STAGE_092\"": [{"id" : 1,"select_type" : "SIMPLE","table" : "test_union_index","partitions" : null,"type" : "index","possible_keys" : null,"key" : "udx_type_status_stage","key_len" : "774","ref" : null,"rows" : 10119,"filtered" : 1.0,"Extra" : "Using where; Using index"}
]}

看似type為index好像走了索引,但實際上possible_keys為null,實際掃描的行數10119,就是全表的行數,根本未走索引。
而真正命中索引的效果為:

{
"explain\r\nselect\r\n\t*\r\nfrom\r\n\ttest_union_index\r\nwhere\r\n\t\r\n\ttask_type=\"TYPE_092\"": [{"id" : 1,"select_type" : "SIMPLE","table" : "test_union_index","partitions" : null,"type" : "ref","possible_keys" : "udx_type_status_stage","key" : "udx_type_status_stage","key_len" : "514","ref" : "const","rows" : 1,"filtered" : 100.0,"Extra" : "Using index"}
]}

3.5.3 合理使用聯合索引避免回表

mysql中同一個索引涉及到的內容維護在同一棵索引樹,如果要查詢的字段包含在索引里且索引生效,那么就可以避免查詢到索引后還需要回查真實數據。

3.6 多種索引沖突

有時SQL太過于復雜,join的表太多,索引之間會有沖突,未必真就會命中預期的索引。這種時候需要從未加索引的狀態開始,一點點加索引做測試,千萬不要偷懶。

4. 跨表JOIN問題

4.1 使用join的基本原則

這是整個Mysql生產中最容易出問題的部分。這里提出如下一些基本思想:

  1. 首先判斷是否只能使用join解決問題,而不能使用任何別的手段。只要還有別的更優方案,盡量避開join
  2. 如果一定要使用join,那么inner join優先其他join(內連接取交集,數據量相對小)
  3. 盡最大可能避免join超過2張表(多表情況,mysql執行器會產生優化障礙和驅動表選擇障礙)
  4. Join on的字段一定要對兩張表都是索引,必須要用等于,比如task_id對于A和B都是索引,那么select data from A inner B where A.task_id=B.taskId
  5. join后的表絕對不要寫select *,一是未必需要全字段,二是不同表也許有同名字段,一旦和實體字段映射錯了那么查詢直接崩潰報錯,三是有時候精確查詢是可以省略回表的,全查則沒有這種可能性
  6. 一定要注意A和B表連接的字段是否是同樣的數據類型,比如A中taskId為varchar,B中為BIGINT,那么就會發生隱式轉換,這是一種函數計算,會導致索引失效
  7. 一定要使用explain分析執行計劃,多方比較,直到explain的所有行都走索引、Extra字段中幾乎沒有using temporary、using where情況才算優化完成

4.2 謂詞下推

在數據庫查詢優化中,謂詞下推(Predicate Pushdown) 是一種通過將過濾條件(謂詞)盡可能提前應用來減少數據處理量的優化技術。其核心思想是:讓數據在進入后續處理階段(如連接、排序、聚合等)之前,就被過濾掉不需要的部分,從而降低整體計算和 IO 開銷。

4.2.1 謂詞

指查詢中用于篩選數據的條件,通常出現在 WHERE、ON 或 HAVING 子句中,例如 age > 30、status = ‘active’ 等

4.2.2 下推

指將這些過濾條件從查詢的 “上層”(如外層查詢、聚合階段)“推” 到 “下層”(如子查詢、表掃描階段、數據源讀取階段)執行,讓過濾操作在數據處理的早期階段完成

4.2.3 為什么需要謂詞下推

沒有謂詞下推時,查詢可能會先讀取大量無關數據,再在后續階段過濾,導致資源浪費。例如:
假設查詢是 A和B join并查詢A.a大于10,B.b小于100 的行:

-- 無優化時的執行邏輯(低效)
select * from A Join B on A.id = B.id where A.a > 10 and B.b < 100;

此時子查詢會先返回表中所有行,再在外部過濾。
應該改為:

select * from (select *  from A  where a>10)A1 Join (select *  from B  where b<100)B1 on A1.id = B1.id;

4.3 不使用join的一些解決思路

4.3.1 從復雜join更替為多個簡單查詢,在代碼中篩選

有時候一條語句實在太過復雜,沒有優化的思路,那么也許可以試試把復雜sql拆成多個簡單查詢。
比如A和B和C表關聯查詢,A表的join字段需要做函數計算導致索引失效。
也許可以嘗試先查一下A表,如果規模并不大,比如結果集只有幾十條上百條,那不妨先查A,而后在代碼中對A的結果做你想要做的函數計算,再把計算結果作為過濾條件參與到B和C的關聯查詢中。

4.3.2 將join改為where中的exists

比如需要查詢A表中符合B表某些條件的數據,如果用join則涉及到關聯,但如果替換為exists,mysql會自動優化為單表查詢,直接優化掉一整張表的掃描,這比任何索引優化都強。

4.3.3 合理冗余字段

如果存儲數據時預先考慮到join可能出現的情況,那么設計表時可以考慮冗余一些字段,比如原本要AjoinB來查詢B中的一兩個字段,那么有沒有可能直接在A中添加這兩個字段來避免JOIN呢?
現在隨著固態硬盤的發展和降價,少量空間上的冗余并不耗費多少成本,但慢SQL一旦出現線上問題,損失可能比存儲貴得多。

5. 分治策略

對于特別復雜的查詢情況,涉及多表多條件,不妨試試看用分治的思想來應對。
核心思路是把一個統一大查詢拆分成幾種不同的分支情況,在代碼里決定不同情況走不同查詢,而后針對不同的查詢專項優化。
比如某種查詢,涉及到AB表關聯查詢,B表查詢條件視情況發生變化,未必出現,最終的結果要按照A表的時間字段進行排序分頁。
那么就可以分出兩種情況:

  1. 不存在B表的查詢條件
  2. 存在
    針對1情況,結合上面的知識,就可以比較容易的得出優化方案,首先把A表做一次查詢和分頁,記為小表再inner joinB表,只要A表查詢中索引設計合理,該查詢的整體效率可以堆到近乎于單表索引查詢的程度。
    針對2情況,再針對B不同查詢條件出現的可能性,針對性調整驅動表、聯合索引等部分。
    這樣,通過部分的優化,逐漸逼近整體的最優效果。

6. 涉及分庫分表

6.1 選擇優良的分庫分表鍵

優良的分庫分表鍵的標準:

  1. 分布均勻:分片鍵需能將數據盡可能均勻地分散到所有分庫分表中,避免某幾個庫 / 表數據量過大(“數據傾斜”)或過小(資源浪費)。例:若用 “用戶性別” 作為分片鍵,僅能分為 2 組,必然導致數據傾斜;而用 “用戶 ID 哈希” 則可分散到 N 個分片。
  2. 匹配高頻查詢場景:分片鍵需與業務中最頻繁的查詢條件強關聯,確保絕大多數查詢能通過分片鍵直接定位到目標庫 / 表,避免 “跨庫全表掃描”。例:訂單查詢 90% 場景是 “按用戶 ID 查詢我的訂單”,則優先用 “用戶 ID” 作為分片鍵,而非 “訂單創建時間”。
  3. 貼合用戶需要:統計業務中 80% 以上的查詢 SQL,提取WHERE條件中最常出現的字段(如用戶 ID、訂單 ID、租戶 ID)。典型案例:電商訂單表中,“用戶 ID” 是高頻查詢字段(用戶查自己的訂單),適合作為分片鍵
  4. 實體唯一標識優先:這類字段天然具備 “唯一性” 和 “穩定性”,且與業務邏輯強綁定,便于理解和維護,如用戶表的user_id、商品表的product_id
  5. 如若必須用多字段組成分庫分表鍵,則建議數據雙寫,雙表查詢。例如:通過 “數據冗余”(如訂單表按用戶 ID 分片,同時冗余一份按時間分片的表)實現。理由:組合分片鍵會增加查詢復雜度,且難以保證所有查詢都能匹配組合條件,容易引發跨庫表查詢

6.2 確保查詢攜帶分庫分表鍵

如果熱點查詢沒有攜帶分庫分表鍵,會觸發跨庫跨表全掃描,這個操作的性能開銷是線上高并發業務不可承受的

7. mysql服務端的配置(DBA操作部分)

有時查詢語句本身囿于業務強需求已經沒有太多優化空間,那么考慮調優服務端配置也是一種思路。
需要注意,這種調整相對復雜,影響比較廣,一定要充分和DBA協商清楚;如果數據庫存在多業務復用,那么所有相關業務方都需要納入討論。

7.1 InnoDB專項優化

7.1.1 InnoDB 緩沖池(innodb_buffer_pool_size)

緩存 InnoDB 表的數據、索引、插入緩沖等,是 MySQL 中最重要的內存配置。命中緩沖池的查詢可直接從內存獲取數據,無需讀磁盤。
對于專用 MySQL 服務器,設置為系統總內存的 50%~70%(預留部分內存給操作系統和其他進程)

7.1.2 日志相關配置

  • innodb_log_buffer_size:事務日志(redo log)的內存緩沖區,滿了會刷盤。
  • innodb_flush_log_at_trx_commit:控制事務日志的刷盤策略(平衡性能與安全性)。
    • 1(默認):每次事務提交立即刷盤,最安全但性能最低(適合金融等核心業務)。
    • 2:事務提交后寫入操作系統緩存,每秒刷盤一次,安全性與性能平衡(推薦)。
    • 0:每秒刷盤一次,性能最高但可能丟失 1 秒內的數據(適合非核心業務)。
  • innodb_log_file_size:單個 redo log 文件的大小。可酌情提高至 512M~2G(需同時調整innodb_log_files_in_group,通常 2~3 個文件),減少日志切換頻率(頻繁切換會導致 IO 波動)。

7.1.3 IO 并發配置

  • innodb_read_io_threads 和 innodb_write_io_threads:控制 InnoDB 的讀寫 IO 線程數,提升 IO 并發處理能力。
  • innodb_flush_method:控制 InnoDB 與磁盤 IO 的交互方式(減少雙重緩存)。建議:Linux 系統:O_DIRECT(繞過操作系統緩存,避免 InnoDB 緩沖池與 OS 緩存的重復緩存)。Windows 系統:async_unbuffered。

7.2 連接與排序緩存

  • join_buffer_size:用于表連接(JOIN)的緩存,非索引連接(ALL或index類型)會使用此緩存。
  • sort_buffer_size:用于ORDER BY或GROUP BY的排序緩存,不足時會使用磁盤臨時文件排序。
  • read_buffer_size:用于順序掃描(filesort)的緩存,提升全表掃描或大范圍查詢的效率。

7.3 臨時表緩存

tmp_table_size 和 max_heap_table_size:控制內存臨時表的最大大小(兩者取較小值),超過則轉為磁盤臨時表(存儲在tmpdir目錄)。
默認值 16M,可酌情提高至 64M~256M(根據業務中臨時表的使用頻率調整),減少磁盤臨時表的 IO 開銷。

8. 硬件上的優化

有時軟件上的優化已經沒有更多空間,這時可以考慮從硬件層面下手把查詢效率帶上來。
需要注意,并非所有云廠商都可以指定mysql服務器的硬件,這一點需要預先確認是否可用以及預算是否支持。

8.1 選擇更合適的CPU

8.1.1 Intel 至強 6 性能核處理器(OLTP 首選)

代表型號:Platinum 6787P(56 核 112 線程,全核睿頻 3.5GHz,L3 緩存 140MB)
核心優勢:
單核性能突出:全核睿頻 3.5GHz,配合 Intel AVX-512 指令集,單線程執行效率比上一代提升 36%,尤其適合高并發事務中的單行查詢、鎖競爭場景。
硬件加速技術:內置數據保護與壓縮加速技術(QAT),可將 SSL 加密和數據壓縮卸載至硬件,減少 CPU 占用率 20%-30%。例如,MySQL 備份速度可提升 2.56 倍。
內存帶寬優化:支持 8 通道 DDR5-6400 內存,帶寬高達 204.8GB/s,比上一代提升 2.3 倍,顯著緩解內存瓶頸。
服務器整合能力:單臺服務器可替代 17 臺舊代服務器,TCO 降低 87%,適合企業級集中式數據庫部署。
典型場景:電商秒殺、金融交易、實時訂單系統等需毫秒級響應的 OLTP 場景。

8.1.2 AMD EPYC 9004 系列(OLAP 首選)

代表型號:Genoa-X 9384X(48 核 96 線程,主頻 2.4GHz,L3 緩存 1.152TB)
核心優勢:
3D V-Cache 技術:堆疊式 L3 緩存容量達 1.152TB,是傳統 CPU 的 3 倍,可將復雜分析查詢的內存訪問延遲降低 50% 以上,尤其適合需頻繁掃描大表的 OLAP 場景。
多核并行處理:64 核 / 128 線程設計,配合 Zen4 架構的高 IPC(每周期指令數),在 sysbench 測試中 OLAP 性能比 Intel 同核數 CPU 提升 27%。
高擴展性:支持 12 通道 DDR5-4800 內存和 128 條 PCIe 5.0 通道,可直連高速 NVMe 存儲或 GPU 加速卡,構建分析型數據湖。
能效比領先:5nm 工藝使功耗比上一代降低 30%,在高密度數據中心中每瓦性能比 Intel 高 60%。
典型場景:供應鏈分析、用戶行為畫像、金融風控等需多表關聯和復雜聚合的 OLAP 場景。

8.2 選擇更合適的硬盤

基本原則:

  1. 固態硬盤 > 機械硬盤
  2. 新一代固態硬盤 > 前代
  3. OLTP 用 PCIE更新的版本
  4. OLAP -> U.2 及以上
  5. 需求均衡 -> SAS 4.0 及以上

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96694.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96694.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96694.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

軟考 系統架構設計師系列知識點之雜項集萃(144)

接前一篇文章:軟考 系統架構設計師系列知識點之雜項集萃(143) 第268題 甲、乙、丙、丁4人加工A、B、C、D四種工件所需工時如下表所示。指派每人加工一種工件,四人加工四種工件其總工時最短的最優方案中,工件B應由()加工。 A B C D 甲

P1168 中位數

題目描述給定一個長度為 N 的非負整數序列 A&#xff0c;對于前奇數項求中位數。輸入格式第一行一個正整數 N。第二行 N 個正整數 A1…N?。輸出格式共 ?2N1?? 行&#xff0c;第 i 行為 A1…2i?1? 的中位數。輸入輸出樣例輸入 #1復制7 1 3 5 7 9 11 6輸出 #11 3 5 6輸入 #…

【CE】圖形化CE游戲教程通關手冊

【CE】圖形化CE游戲教程通關手冊 文章目錄【CE】圖形化CE游戲教程通關手冊導讀需求1?? 第一關提示操作總結2?? 第二關&#xff08;代碼共享&#xff09;提示操作驗證3?? 第三關提示提示總結導讀 需求 除了Tutorial-x86_64.exe教程外&#xff0c;CE還提供了圖形化教程gtu…

leetcode 2785. 將字符串中的元音字母排序 中等

給你一個下標從 0 開始的字符串 s &#xff0c;將 s 中的元素重新 排列 得到新的字符串 t &#xff0c;它滿足&#xff1a;所有輔音字母都在原來的位置上。更正式的&#xff0c;如果滿足 0 < i < s.length 的下標 i 處的 s[i] 是個輔音字母&#xff0c;那么 t[i] s[i] 。…

支付子系統架構及常見問題

支付流程對于支付系統來說&#xff0c;它最重要的其實是安全&#xff0c;所以整個支付流程采用秘鑰加簽的方式進行操作&#xff0c;一共四對秘鑰&#xff0c;以支付寶在線支付為例子&#xff0c;首先通過RSA2算法生成商戶公鑰以及商戶私鑰&#xff0c;同時支付寶平臺會提供支付…

內存傳輸速率MT/s

1 0 0 0 0 0 0 0 0 010 9 8 7 6 5 4 3 2 1十 億 千 百 十 萬 千 百 十 個億 萬 萬 萬傳輸速率 …

.env文件的作用和使用方法

目錄 什么是 .env 文件&#xff1f; 為什么要使用 .env 文件&#xff1f;&#xff08;好處&#xff09; 如何使用 .env 文件&#xff1f; 通用步驟&#xff1a; 具體技術棧中的實現&#xff1a; 最佳實踐和注意事項 總結 什么是 .env 文件&#xff1f; .env 文件&#x…

深度拆解 Python 裝飾器參數傳遞:從裝飾器生效到參數轉交的每一步

在 Python 裝飾器的學習中&#xff0c;“被裝飾函數的參數如何傳遞到裝飾器內層函數”是一個高頻疑問點。很多開發者能寫出裝飾器的基本結構&#xff0c;卻對參數傳遞的底層邏輯一知半解。本文將以一段具體代碼為例&#xff0c;把參數傳遞過程拆成“裝飾器生效→調用觸發→參數…

【Vue2 ?】Vue2 入門之旅 · 進階篇(七):Vue Router 原理解析

在前幾篇文章中&#xff0c;我們介紹了 Vue 的性能優化機制、組件緩存等內容。本篇將深入解析 Vue Router 的原理&#xff0c;了解 Vue 如何管理路由并進行導航。 目錄 Vue Router 的基本概念路由模式&#xff1a;hash 和 history路由匹配原理導航守衛Vue Router 的路由過渡動…

Linux磁盤級文件/文件系統理解

Linux磁盤級文件/文件系統理解 1. 磁盤的物理結構 磁盤的核心是一個利用磁性介質和機械運動進行數據讀寫的、非易失性的存儲設備。 1.1 盤片 盤片是傳統機械硬盤中最核心的部件&#xff0c;它是數據存儲的物理載體。盤片是一個堅硬的、表面極度光滑的圓形碟片&#xff0c;被安裝…

【星海出品】rabbitMQ - 叁 應用篇

rabbitMQ 的基礎知識這里就不闡述了,可以參看我早年寫的文章 -> rabbitMQ 入門 https://blog.csdn.net/weixin_41997073/article/details/118724779 Celery 官網:http://www.celeryproject.org/ Celery 官方文檔英文版:http://docs.celeryproject.org/en/latest/index.h…

C# 每個chartArea顯示最小值、平均值、最大值

private void AddStatisticsAnnotations(ChartArea chartArea, int channelIndex) {RemoveExistingAnnotations(channelIndex);// 獲取ChartArea的相對坐標&#xff08;百分比&#xff09;float chartAreaX chartArea.Position.X; // X坐標&#xff08;百分比&#xff09;floa…

打破“不可能三角”:WALL-OSS開源,具身智能迎來“安卓時刻”?

目錄 引言&#xff1a;當“大腦”學會思考&#xff0c;機器人才能走出實驗室 一、具身智能的“不可能三角”&#xff1a;機器人“大腦”的核心困境 二、WALL-OSS的四把重錘&#xff1a;如何系統性地破解難題&#xff1f; 2.1 第一錘&#xff1a;更聰明的“大腦”架構 —— …

SigNoz分布式追蹤新體驗:cpolar實現遠程微服務監控

前言 SigNoz是一款開源的應用性能監控工具&#xff0c;專為微服務架構設計&#xff0c;集成了指標、追蹤和日志分析功能。它能夠全面監控分布式系統的性能&#xff0c;幫助開發團隊快速定位問題根源。SigNoz支持OpenTelemetry協議&#xff0c;可以無縫集成各種編程語言和框架&…

python編程原子化多智能體綜合編程應用(下)

上述代碼實現了基于Mesa框架的診斷智能體類,包含以下核心功能: 模塊化設計:通過類屬性分離數據與行為,支持不同專科智能體的擴展 狀態管理:實現idle/processing/error等狀態轉換,支持任務調度 診斷推理:集成機器學習模型,支持癥狀提取與多分類診斷 錯誤處理:包含模型加…

QT M/V架構開發實戰:QSqlQueryModel/ QSqlTableModel/ QSqlRelationalTableModel介紹

目錄[TOC](目錄)前言一、初步介紹二、QSqlQueryModel1.基礎定位2.特點3.核心接口4.典型用法5.優缺點三、QSqlTableModel1.基礎定位2.特點3.核心接口4.典型用法5.優缺點四、QSqlRelationalTableModel1.基礎定位2.特點3.核心接口4.典型用法 (示例&#xff1a;employees表有 dept_…

Terraform 從入門到實戰:歷史、原理、功能與阿里云/Azure 上手指南

前言&#xff1a;在云時代&#xff0c;企業的IT基礎設施早已從“幾臺服務器”演變為“橫跨多云的復雜網絡、計算、存儲集群”。但隨之而來的&#xff0c;是管理復雜度的爆炸式增長&#xff1a;開發環境和生產環境不一致、手動配置容易出錯、多云平臺操作方式各異、資源變更難以…

【計算機網絡 | 第10篇】信道復用技術

文章目錄信道復用技術&#xff1a;高效利用通信資源的智慧方案一、頻分復用&#xff08;FDM&#xff09;&#xff1a;按頻率劃分的并行通道二、時分復用&#xff08;TDM&#xff09;&#xff1a;按時間分割的輪流占用三、統計時分復用&#xff08;STDM&#xff09;&#xff1a;…

安卓13_ROM修改定制化-----禁用 Android 導航按鍵的幾種操作

Android 設備的導航按鍵通常包括后退鍵(Back)、主頁鍵(Home)和最近鍵(Recents),這些按鍵位于屏幕底部或設備實體區域。禁用導航按鍵可以幫助在特定應用場景(如信息亭模式或兒童鎖模式)中限制用戶操作。安卓設備上禁用底部虛擬導航鍵(返回、主頁、多任務鍵)有多種方法…

通過S參數測量評估電感阻抗:第2部分

S21雙端口分流和雙端口串聯方法 T這是兩篇文章中的第二篇&#xff0c;專門討論使用網絡分析儀測量 S 參數進行電感阻抗評估主題。上一篇文章 [1] 描述了阻抗測量和計算S11使用單端口分流器、雙端口分流器和雙端口串聯方法的參數。本文專門介紹阻抗測量和計算S21使用雙端口分流…