最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot數(shù)據(jù)庫索引優(yōu)化指南

 更新時間:2026年04月22日 08:53:19   作者:希望永不加班  
本文詳細介紹了MySQL慢查詢?nèi)罩镜呐渲门c分析方法,包括動態(tài)配置和修改配置文件兩種方式,以及SpringBoot項目中打印SQL執(zhí)行日志的配置,需要的朋友可以參考下

前面我們已經(jīng)完整攻克了整套緩存體系:從緩存雙寫一致性的3種落地策略、Caffeine本地緩存與Redis分布式緩存的多級架構(gòu)整合,到分布式多實例緩存同步的Redis發(fā)布訂閱方案,每一步都貼合企業(yè)級高并發(fā)落地標準。

緩存作為系統(tǒng)性能優(yōu)化的“上層手段”,核心作用是減輕數(shù)據(jù)庫的查詢壓力、縮短接口響應(yīng)時間,但很多開發(fā)同學會陷入一個致命誤區(qū):只要加了緩存,系統(tǒng)性能就一定能達標。

實際上,在企業(yè)真實生產(chǎn)項目中,80%以上的系統(tǒng)性能瓶頸,根源都不在緩存,而在底層MySQL數(shù)據(jù)庫本身。小數(shù)據(jù)量(幾萬條以內(nèi))場景下,哪怕是全表掃描、劣質(zhì)SQL,也能做到毫秒級響應(yīng),看不出任何問題;但一旦單表數(shù)據(jù)量突破幾十萬、幾百萬,甚至上千萬,沒有合理的索引、不規(guī)范的SQL寫法、大量慢查詢,會直接導(dǎo)致接口耗時從幾十毫秒飆升到幾百毫秒、幾秒,甚至幾十秒,MySQL的CPU、磁盤IO會被直接打滿,數(shù)據(jù)庫連接池耗盡。

更關(guān)鍵的是:哪怕你的緩存架構(gòu)設(shè)計得再完美,底層數(shù)據(jù)庫本身扛不住流量,緩存也會失去意義——一旦緩存失效、擊穿,海量請求會瞬間涌入早已不堪重負的數(shù)據(jù)庫,直接引發(fā)系統(tǒng)雪崩。

這里必須強調(diào)一個性能優(yōu)化的核心優(yōu)先級:先優(yōu)化SQL與數(shù)據(jù)庫索引,再做緩存優(yōu)化,最后做架構(gòu)層面(分庫分表、讀寫分離)的優(yōu)化。數(shù)據(jù)庫是系統(tǒng)性能的“根基”,只有把根基打牢,緩存才能真正發(fā)揮最大價值,否則一切都是空中樓閣。

一、SpringBoot 環(huán)境下開啟MySQL慢查詢?nèi)罩?/h2>

想要優(yōu)化慢查詢,第一步永遠是“精準定位慢SQL”——只有找到所有執(zhí)行耗時過長的SQL,才能針對性地進行優(yōu)化。MySQL內(nèi)置了完善的慢查詢?nèi)罩竟δ?,能夠自動記錄所有超過閾值的SQL,包含完整的執(zhí)行明細,是線上排查慢查詢的最權(quán)威工具。

結(jié)合SpringBoot項目的開發(fā)、生產(chǎn)環(huán)境,我們提供兩種配置方式,適配不同場景,全部可直接復(fù)制落地。

1. 方式一:SQL動態(tài)配置(線上推薦,無需重啟MySQL)

線上生產(chǎn)環(huán)境禁止隨意重啟MySQL服務(wù)(重啟會導(dǎo)致服務(wù)中斷),因此我們優(yōu)先使用SQL命令動態(tài)開啟慢查詢?nèi)罩?,即時生效,無需重啟數(shù)據(jù)庫,排查完成后還可以動態(tài)關(guān)閉,不影響線上服務(wù)。

完整動態(tài)配置SQL

-- 1. 開啟慢查詢?nèi)罩荆?=開啟,0=關(guān)閉),全局生效
SETGLOBAL slow_query_log =ON;
-- 2. 設(shè)置慢查詢閾值,單位:秒,這里設(shè)置為0.5秒(500ms),適配普通業(yè)務(wù)接口
SETGLOBAL long_query_time =0.5;
-- 3. 開啟“記錄未使用索引的查詢”(非常關(guān)鍵!即使SQL執(zhí)行耗時未超過閾值,只要沒走索引,也會被記錄)
-- 避免遺漏“隱性慢查詢”(比如數(shù)據(jù)量增長后,未走索引的SQL會逐漸變成慢查詢)
SETGLOBAL log_queries_not_using_indexes =ON;
-- 4. 設(shè)置慢查詢?nèi)罩镜拇鎯β窂剑蛇x,默認路徑可通過SHOW VARIABLES查看)
-- 注意:路徑需確保MySQL用戶有讀寫權(quán)限,避免日志無法生成
SETGLOBAL slow_query_log_file ='/var/lib/mysql/slow.log';
-- 5. 設(shè)置日志輸出格式(可選,默認FILE,即輸出到文件;可設(shè)置為TABLE,存儲到mysql.slow_log表中)
SETGLOBAL log_output ='FILE,TABLE';
-- 6. 查看所有慢查詢配置是否生效(驗證配置)
SHOW VARIABLES LIKE'%slow_query%';  -- 查看慢查詢?nèi)罩鞠嚓P(guān)配置
SHOW VARIABLES LIKE'long_query_time';  -- 查看慢查詢閾值
SHOW VARIABLES LIKE'log_queries_not_using_indexes';  -- 查看未走索引查詢的記錄配置

注意事項

  • 修改 GLOBAL 全局參數(shù)后,需要重新斷開數(shù)據(jù)庫連接(比如重啟SpringBoot服務(wù)、重新連接Navicat),新的連接才會加載最新的配置;已存在的連接,依然使用舊的配置。
  • 線上環(huán)境排查完成后,建議關(guān)閉“記錄未使用索引的查詢”(SET GLOBAL log_queries_not_using_indexes = OFF),避免大量無索引的普通SQL占用日志空間,影響慢查詢?nèi)罩镜目勺x性。
  • 如果慢查詢?nèi)罩疚募^大(超過1G),可以使用 mysqldumpslow 工具進行分析,或者手動清空日志(echo "" > /var/lib/mysql/slow.log),避免占用過多磁盤空間。

2. 方式二:修改MySQL配置文件(永久生效,適合開發(fā)/測試環(huán)境)

適合開發(fā)環(huán)境、測試環(huán)境,或者新項目初始化配置,修改MySQL的配置文件后,重啟MySQL服務(wù)即可永久生效,無需每次手動執(zhí)行SQL配置。

不同系統(tǒng)的配置文件路徑

  • Linux系統(tǒng)(CentOS、Ubuntu):/etc/my.cnf 或 /etc/mysql/my.cnf;
  • Windows系統(tǒng):MySQL安裝目錄/my.ini(比如 C:Program FilesMySQLMySQL Server 8.0my.ini);
  • Docker部署的MySQL:需要掛載配置文件,或者進入容器內(nèi)部修改 /etc/my.cnf

完整配置內(nèi)容

[mysqld]
# 開啟慢查詢?nèi)罩荆ū靥睿?
slow_query_log = ON
# 慢查詢閾值,單位:秒,設(shè)置為0.5秒(500ms)(必填)
long_query_time = 0.5
# 慢查詢?nèi)罩敬鎯β窂剑ū靥?,確保路徑可寫)
slow_query_log_file = /var/lib/mysql/slow.log
# 記錄所有未使用索引的查詢(開發(fā)環(huán)境建議開啟,線上排查時開啟,平時可關(guān)閉)
log_queries_not_using_indexes = ON
# 日志輸出格式:FILE(輸出到文件)+ TABLE(存儲到mysql.slow_log表),方便多方式查看
log_output = FILE,TABLE
# 忽略系統(tǒng)數(shù)據(jù)庫(mysql、information_schema等)的慢查詢,避免日志冗余
ignore_db_dirs = mysql,information_schema,performance_schema,sys
# 記錄慢查詢的詳細信息(可選,默認開啟)
log_slow_admin_statements = ON# 記錄管理員操作中的慢查詢(如alter table)
log_slow_slave_statements = ON# 主從復(fù)制場景下,記錄從庫的慢查詢

配置生效步驟

1. 修改配置文件后,保存退出;

2. 重啟MySQL服務(wù)(不同系統(tǒng)重啟命令不同):

  • Linux(CentOS):systemctl restart mysqld;
  • Linux(Ubuntu):systemctl restart mysql
  • Windows:在服務(wù)中找到“MySQL”,右鍵重啟;
  • Docker:docker restart 容器ID

3. 重啟后,連接MySQL,執(zhí)行 SHOW VARIABLES LIKE '%slow_query%',驗證配置是否生效。

3. SpringBoot 項目配置:打印SQL執(zhí)行日志(本地開發(fā)調(diào)試)

開發(fā)環(huán)境中,我們可以通過配置SpringBoot的日志,直接打印SQL的執(zhí)行語句、執(zhí)行耗時,方便本地快速排查慢SQL,無需依賴MySQL的慢查詢?nèi)罩尽?/p>

以下配置適配MyBatis、MyBatis-Plus,復(fù)制到 application.yml 即可生效:

spring:
  datasource:
    # 數(shù)據(jù)庫連接配置(替換為自己的數(shù)據(jù)庫信息)
    url:jdbc:mysql://localhost:3306/springboot_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username:root
    password:123456
    driver-class-name:com.mysql.cj.jdbc.Driver
# MyBatis-Plus 配置(如果使用原生MyBatis,配置類似)
mybatis-plus:
mapper-locations:classpath:mapper/*.xml# mapper文件路徑
type-aliases-package:com.xxx.entity# 實體類包路徑
configuration:
    # 打印完整SQL語句、執(zhí)行耗時(本地開發(fā)開啟,線上關(guān)閉)
    log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
    # 開啟駝峰命名映射(可選,避免字段名與實體類屬性不匹配)
    map-underscore-to-camel-case:true
# 日志配置(可選,細化日志輸出,避免冗余)
logging:
level:
    # 打印指定包下的SQL日志(替換為自己的mapper包路徑)
    com.xxx.mapper:debug
    # 關(guān)閉其他無關(guān)日志,提升可讀性
    org.springframework:warn
    com.baomidou.mybatisplus:warn

配置生效后,啟動SpringBoot項目,執(zhí)行接口請求,控制臺會輸出類似如下日志,清晰看到SQL執(zhí)行耗時:

==>  Preparing: SELECT id,name,age,create_time FROM user WHERE id = ? 
==> Parameters: 1(Long)
<==    Columns: id, name, age, create_time
<==        Row: 1, 張三, 25, 2024-01-01 10:00:00
<==      Total: 1
<==  Updates: 0
<==   Elapsed: 12.35 ms  # 執(zhí)行耗時,一目了然

4. 慢查詢?nèi)罩静榭磁c分析工具

慢查詢?nèi)罩旧珊?,我們需要對日志進行分析,提取出耗時最長、執(zhí)行最頻繁的慢SQL,針對性優(yōu)化。這里推薦4種常用工具,適配不同場景。

(1)原生日志查看

直接通過命令行查看慢查詢?nèi)罩?,適合線上臨時排查,無需安裝額外工具:

# 1. 實時查看慢查詢?nèi)罩荆ㄗ钚碌穆齋QL會實時輸出)
tail -f /var/lib/mysql/slow.log
# 2. 查看日志的前10行(快速了解日志格式)
head -n 10 /var/lib/mysql/slow.log
# 3. 統(tǒng)計日志中所有慢SQL的數(shù)量
grep -c "Query_time" /var/lib/mysql/slow.log
# 4. 查找耗時超過1秒的慢SQL
grep "Query_time>1" /var/lib/mysql/slow.log

(2)mysqldumpslow

MySQL自帶的慢查詢?nèi)罩痉治龉ぞ撸瑹o需額外安裝,能夠?qū)β齋QL進行匯總、排序,快速找到最耗時、最頻繁的慢SQL,線上最常用。

常用命令(復(fù)制可用):

# 1. 按執(zhí)行耗時排序,查看耗時最高的10條慢SQL(最常用)
mysqldumpslow -s t -n 10 /var/lib/mysql/slow.log
# 2. 按執(zhí)行次數(shù)排序,查看最頻繁執(zhí)行的10條慢SQL
mysqldumpslow -s c -n 10 /var/lib/mysql/slow.log
# 3. 按鎖定時間排序,查看鎖定時間最長的10條慢SQL
mysqldumpslow -s l -n 10 /var/lib/mysql/slow.log
# 4. 過濾指定數(shù)據(jù)庫的慢SQL(比如只查看springboot_demo庫的慢SQL)
mysqldumpslow -d springboot_demo /var/lib/mysql/slow.log
# 5. 輸出詳細的慢SQL信息(包含執(zhí)行時間、掃描行數(shù)、返回行數(shù))
mysqldumpslow -v /var/lib/mysql/slow.log

命令參數(shù)說明:-s 表示排序方式(t=耗時、c=次數(shù)、l=鎖定時間),-n 表示顯示的條數(shù),-d 表示指定數(shù)據(jù)庫,-v 表示顯示詳細信息。

(3)pt-query-digest

Percona Toolkit中的核心工具,比 mysqldumpslow 功能更強大,能夠?qū)β樵內(nèi)罩具M行深度分析,生成詳細的統(tǒng)計報告,適合慢SQL數(shù)量多、場景復(fù)雜的線上環(huán)境。

安裝命令(Linux):yum install percona-toolkit -y(CentOS)、apt install percona-toolkit -y(Ubuntu)。

常用命令:

# 分析慢查詢?nèi)罩?,生成詳細報告(輸出到屏幕?
pt-query-digest /var/lib/mysql/slow.log
# 分析慢查詢?nèi)罩?,將報告輸出到文件(方便后續(xù)查看)
pt-query-digest /var/lib/mysql/slow.log > slow_query_analysis.log

報告核心信息:會按SQL執(zhí)行頻率、耗時排序,標注每條SQL的掃描行數(shù)、返回行數(shù)、執(zhí)行用戶、執(zhí)行時間,甚至會給出優(yōu)化建議,非常實用。

(4)可視化工具

開發(fā)環(huán)境中,我們可以使用可視化工具查看慢查詢?nèi)罩?,操作簡單、直觀:

注意:線上環(huán)境建議使用 mysqldumpslow 或 pt-query-digest 分析慢查詢?nèi)罩?,避免使用可視化工具(需要連接線上數(shù)據(jù)庫,存在安全風險,且可能占用數(shù)據(jù)庫資源)。

二、Explain 執(zhí)行計劃全字段詳解

找到慢SQL后,下一步就是分析“為什么這條SQL執(zhí)行慢”——核心工具就是 explain 執(zhí)行計劃。

explain 是MySQL提供的一個核心命令,在SQL語句前加上 explain,可以查看MySQL優(yōu)化器對這條SQL的執(zhí)行計劃,包括:SQL的執(zhí)行方式(全表掃描還是索引掃描)、使用了哪個索引、掃描了多少行數(shù)據(jù)、返回多少行數(shù)據(jù)、是否使用了臨時表、是否進行了排序等關(guān)鍵信息。

掌握 explain 的使用,是區(qū)分“新手”和“資深開發(fā)者”的關(guān)鍵,也是面試高頻考點。下面我們結(jié)合SpringBoot項目中的真實SQL,逐字段詳解 explain 執(zhí)行計劃,確保每個人都能看懂、會用。

1. Explain 基本使用方法

使用非常簡單,在需要分析的SQL語句前加上 explain 即可,示例:

-- 分析單表查詢
EXPLAIN SELECT id, name, age FROMuserWHERE age >20;
-- 分析多表關(guān)聯(lián)查詢
EXPLAIN SELECT u.id, u.name, o.order_no FROMuser u LEFTJOIN `order` o ON u.id = o.user_id WHERE u.age >20;
-- 分析更新、刪除語句(查看執(zhí)行計劃,判斷是否走索引)
EXPLAIN UPDATEuserSET name ='李四'WHERE id =1;

執(zhí)行后,MySQL會返回一個包含12個字段的表格,每個字段都對應(yīng)SQL執(zhí)行的關(guān)鍵信息,我們逐一拆解。

2. Explain 12個字段逐字詳解

我們以SpringBoot項目中的商品表(product)為例,表結(jié)構(gòu)如下(復(fù)制可創(chuàng)建):

CREATE TABLE `product` (
  `id` bigintNOT NULL AUTO_INCREMENT COMMENT '商品ID(主鍵)',
  `name` varchar(100) NOT NULL COMMENT '商品名稱',
  `category_id` bigintNOT NULL COMMENT '分類ID',
  `price` decimal(10,2) NOT NULL COMMENT '商品價格',
  `stock` intNOT NULL COMMENT '庫存',
  `create_time` datetime NOT NULL COMMENT '創(chuàng)建時間',
  `update_time` datetime NOT NULL COMMENT '更新時間',
PRIMARY KEY (`id`),
  KEY `idx_category_id` (`category_id`),  -- 分類ID索引
  KEY `idx_create_time` (`create_time`)  -- 創(chuàng)建時間索引
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

我們以這條SQL為例,分析 explain 執(zhí)行計劃:EXPLAIN SELECT id, name, price FROM product WHERE category_id = 10 AND create_time > &#39;2024-01-01&#39;

執(zhí)行后返回的執(zhí)行計劃表格,以及每個字段的詳細說明如下(重點字段標紅):

(1)id:SQL執(zhí)行的順序標識

核心作用:標識SQL語句中每個查詢塊的執(zhí)行順序,有以下三種情況:

示例中,id=1,說明只有一個查詢塊,按順序執(zhí)行即可。

(2)select_type:查詢類型

標識當前查詢的類型,決定了查詢的復(fù)雜度和執(zhí)行方式,常見值及說明(重點記前5個):

select_type

說明

實戰(zhàn)場景

SIMPLE

簡單查詢,無子查詢、無union

SELECT * FROM product WHERE id = 1

PRIMARY

主查詢,包含子查詢時,最外層的查詢

SELECT * FROM user WHERE id IN (SELECT user_id FROM order)

SUBQUERY

子查詢,嵌套在主查詢中的查詢(不依賴主查詢結(jié)果)

同上,子查詢 SELECT user_id FROM order

DERIVED

派生表查詢,子查詢返回的結(jié)果作為臨時表

SELECT * FROM (SELECT id FROM product) AS t

UNION

union查詢的第二個及以后的查詢

SELECT id FROM user UNION SELECT id FROM product

UNION RESULT

union查詢的結(jié)果匯總

同上,匯總兩個查詢的結(jié)果

示例中,select_type=SIMPLE,說明是簡單查詢,無復(fù)雜嵌套。

(3)table:當前查詢涉及的表

顯示當前查詢塊正在操作的表名,如果是子查詢、派生表,會顯示臨時表的名稱(如derived2、union1)。

示例中,table=product,說明當前查詢操作的是商品表。

(4)type:訪問類型(判斷是否走索引)

這是 explain 中最核心的字段,標識MySQL訪問表的方式,即“如何獲取數(shù)據(jù)”,決定了查詢的效率,按效率從高到低排序(重點記前6個):

面試必背:type 字段的優(yōu)化目標是“至少達到 range 級別,最好達到 ref 或 const 級別”,如果出現(xiàn) ALL(全表掃描),說明沒有走索引,需要優(yōu)先優(yōu)化。

示例中,type=ref,說明通過普通索引 idx_category_id 查詢,效率較高。

(5)possible_keys:可能使用的索引

顯示MySQL優(yōu)化器認為當前查詢“可能”使用的索引,不一定會實際使用(可能有多個,用逗號分隔)。

示例中,possible_keys=idx_category_id,idx_create_time,說明優(yōu)化器認為可能使用分類ID索引或創(chuàng)建時間索引。

(6)key:實際使用的索引

顯示MySQL優(yōu)化器實際使用的索引,如果為NULL,說明沒有使用任何索引(走全表掃描)。

示例中,key=idx_category_id,說明實際使用的是分類ID索引,與possible_keys中的一個一致。

關(guān)鍵注意:如果 possible_keys 有值,但 key 為 NULL,說明索引建立不合理,或者SQL寫法有問題,導(dǎo)致優(yōu)化器放棄使用索引。

(7)key_len:實際使用的索引長度(單位:字節(jié))

核心作用:判斷索引的使用情況,尤其是聯(lián)合索引,通過key_len可以判斷聯(lián)合索引使用了哪些字段(遵循最左前綴匹配原則)。

計算規(guī)則(簡單記):
varchar(100):utf8mb4編碼,每個字符占4字節(jié),100*4=400字節(jié),加上null標識(1字節(jié)),共401字節(jié);bigint:8字節(jié),int:4字節(jié),datetime:8字節(jié);如果字段為NOT NULL,不需要null標識,減少1字節(jié)。

示例中,key=idx_category_id(category_id是bigint NOT NULL),key_len=8,符合計算規(guī)則,說明索引使用正常。

(8)ref:與索引匹配的列或常量

顯示與當前使用的索引匹配的列名,或者常量值,說明索引是如何被使用的。

示例中,ref=const,說明category_id=10(常量),與索引idx_category_id匹配,符合查詢條件。

(9)rows:MySQL預(yù)估要掃描的行數(shù)

顯示MySQL優(yōu)化器預(yù)估的、需要掃描的行數(shù),不是實際掃描的行數(shù),但能反映查詢的效率——行數(shù)越少,查詢效率越高。

示例中,rows=100,說明優(yōu)化器預(yù)估需要掃描100行數(shù)據(jù)就能找到符合條件的結(jié)果;如果rows=100000,說明需要掃描10萬行數(shù)據(jù),效率極低,大概率是全表掃描。

關(guān)鍵注意:如果rows數(shù)值很大,但實際返回的行數(shù)很少,說明索引建立不合理,或者查詢條件過濾性差,需要優(yōu)化。

(10)Extra:額外信息

這是 explain 中最靈活、最有價值的字段,包含了SQL執(zhí)行的額外細節(jié),很多慢查詢的問題都能從這里找到原因,常見值及說明(重點記紅框內(nèi)的):

? 理想狀態(tài)(優(yōu)化到位):

Using index:使用了覆蓋索引(查詢的字段都在索引中,無需回表查詢數(shù)據(jù)),效率極高,是優(yōu)化的目標;

Using where:使用了where條件過濾數(shù)據(jù),過濾效果較好;

Using index condition:使用了索引條件推送(ICP),減少回表查詢的次數(shù),提升效率。

? 需要優(yōu)化的狀態(tài)(慢查詢常見):

Using filesort:無法使用索引排序,需要在磁盤或內(nèi)存中進行排序(文件排序),耗時極長,尤其是數(shù)據(jù)量大時;

Using temporary:需要創(chuàng)建臨時表存儲查詢結(jié)果,再進行后續(xù)操作(比如group by、distinct、union),耗時較長;

Using join buffer:多表關(guān)聯(lián)時,沒有使用索引,需要使用連接緩沖區(qū)存儲關(guān)聯(lián)數(shù)據(jù),效率低;

Using where; Using filesort:使用了where過濾,但排序沒有使用索引,需要優(yōu)化排序字段的索引;

Using where; Using temporary; Using filesort:最糟糕的情況,需要創(chuàng)建臨時表、進行文件排序,必須優(yōu)先優(yōu)化。

示例中,Extra=Using index condition; Using where,說明使用了索引條件推送和where過濾,執(zhí)行效率較好。

(11)filtered:過濾比例(百分比)

顯示經(jīng)過where條件過濾后,剩余數(shù)據(jù)占總掃描行數(shù)的比例,比例越高,說明過濾效果越好(查詢條件越精準)。

示例中,filtered=80,說明經(jīng)過where條件過濾后,剩余80%的掃描行數(shù)是符合條件的,過濾效果較好;如果filtered=1,說明過濾效果極差,大部分掃描的行數(shù)都不符合條件,需要優(yōu)化查詢條件。

(12)partitions:分區(qū)表相關(guān)(可選)

如果表是分區(qū)表,顯示當前查詢涉及的分區(qū);如果不是分區(qū)表,顯示為NULL。

示例中,partitions=NULL,說明商品表不是分區(qū)表。

3. Explain 實戰(zhàn)案例(判斷慢SQL原因)

結(jié)合上面的字段詳解,我們用一個實戰(zhàn)案例,演示如何通過 explain 分析慢SQL的原因。

案例:慢SQL語句

-- 商品表有80萬條數(shù)據(jù),查詢分類ID為10、價格大于100的商品,耗時1.2s
SELECT * FROM product WHERE category_id = 10 AND price > 100;

執(zhí)行 Explain 后的關(guān)鍵字段:

分析原因:

雖然建立了 idx_category_id 索引,但查詢條件中包含 price > 100,而 price 字段沒有建索引,且 idx_category_id 是單字段索引,MySQL優(yōu)化器判斷“使用索引后,還需要回表查詢price字段,再過濾”,效率不如直接全表掃描,因此放棄使用索引,導(dǎo)致慢查詢。

優(yōu)化方案:

建立聯(lián)合索引 idx_category_price(category_id, price),遵循最左前綴匹配原則,查詢條件中的category_id在前,price在后,能夠直接命中索引,同時過濾兩個條件,無需回表。

優(yōu)化后 Explain 關(guān)鍵字段:

優(yōu)化后,SQL執(zhí)行耗時從1.2s降至20ms,性能提升60倍。

面試實戰(zhàn)題:如何通過 Explain 判斷SQL是否走索引?如何判斷慢SQL的原因?(標準回答)

三、InnoDB索引底層原理

很多開發(fā)同學只會建索引、用索引,卻不懂索引的底層原理,導(dǎo)致遇到索引失效、性能不達標的問題時,無法從根源上解決。想要真正做好索引優(yōu)化,必須先吃透InnoDB索引的底層實現(xiàn)——畢竟,所有的索引優(yōu)化技巧,都源于對底層原理的理解。

InnoDB是MySQL最常用的存儲引擎(企業(yè)生產(chǎn)環(huán)境首選),其索引底層基于B+樹實現(xiàn),這也是MySQL索引高效的核心原因。下面我們不搞復(fù)雜的理論堆砌,結(jié)合實戰(zhàn)場景,拆解B+樹索引的核心特性、索引類型及底層存儲邏輯,重點解決“為什么這么建索引高效”“為什么有些索引會失效”的問題。

1. InnoDB B+樹索引核心特性

B+樹是一種平衡多路查找樹,InnoDB對其進行了優(yōu)化,使其更適配數(shù)據(jù)庫的讀寫場景,核心特性如下(直接決定索引的使用效率):

2. InnoDB 兩大索引類型:聚簇索引 vs 非聚簇索引

InnoDB有兩種核心索引類型,兩者的存儲邏輯、查詢效率差異極大,很多慢查詢都源于對這兩種索引的混淆,必須嚴格區(qū)分。

(1)聚簇索引(主鍵索引,Clustered Index)

聚簇索引是InnoDB的核心索引,也是表的“主索引”,每個表只能有一個聚簇索引,其底層存儲邏輯如下:

舉個例子:查詢select * from product where id = 10(id是主鍵,聚簇索引),MySQL會通過B+樹找到id=10的葉子節(jié)點,直接讀取該節(jié)點中的完整商品數(shù)據(jù),無需額外操作,耗時通常在10ms以內(nèi)。

(2)非聚簇索引(二級索引,Secondary Index)

非聚簇索引是除聚簇索引以外的所有索引(如普通索引、聯(lián)合索引、唯一索引),也叫二級索引,一個表可以有多個非聚簇索引,其底層存儲邏輯與聚簇索引完全不同:

舉個例子:查詢select * from product where category_id = 10(category_id是普通索引,非聚簇索引),查詢流程如下:

關(guān)鍵結(jié)論:非聚簇索引查詢會多一次回表操作,比聚簇索引查詢效率低;如果能避免回表查詢,就能大幅提升非聚簇索引的查詢效率——這就是“覆蓋索引”的核心價值(后面會詳細講解)。

面試必背:聚簇索引與非聚簇索引的核心區(qū)別?

3. 回表查詢與覆蓋索引(優(yōu)化非聚簇索引的核心)

通過上面的講解,我們知道:非聚簇索引的查詢效率低,核心原因是“回表查詢”——多一次磁盤IO操作,尤其是數(shù)據(jù)量大、查詢頻繁時,回表會嚴重拖慢查詢性能。而解決這個問題的核心方案,就是覆蓋索引。

(1)回表查詢的危害

假設(shè)商品表(product)有80萬條數(shù)據(jù),非聚簇索引idx_category_id(category_id),執(zhí)行查詢select * from product where category_id = 10

可見,回表查詢的磁盤IO開銷極大,是慢查詢的常見誘因之一,而覆蓋索引能徹底解決這個問題。

(2)覆蓋索引的定義與實戰(zhàn)用法

定義:如果非聚簇索引的索引鍵,包含了查詢語句中所有需要的字段(select后面的字段),那么通過這個非聚簇索引查詢時,無需回表,直接從索引中獲取所有數(shù)據(jù),這個非聚簇索引就是覆蓋索引。

核心邏輯:讓非聚簇索引的葉子節(jié)點,不僅存儲主鍵值,還存儲查詢所需的其他字段,從而避免回表。

實戰(zhàn)案例(延續(xù)前文商品表):

(3)覆蓋索引的使用技巧

4. 索引失效的底層原因

前面我們提到“SQL寫法不規(guī)范、索引建立不合理會導(dǎo)致索引失效”,但背后的底層原因,都與InnoDB B+樹索引的存儲邏輯有關(guān),總結(jié)3個核心底層原因:

如果你在實戰(zhàn)中遇到問題,歡迎在評論區(qū)留言交流,一起避坑、一起進步!

別忘了點贊+在看+收藏三連,關(guān)注我,解鎖更多 SpringBoot AOP 實戰(zhàn)干貨,下期再見??

• Navicat:連接MySQL后,點擊「工具」→「慢查詢?nèi)罩尽?,即可查看、篩選慢SQL;

• IDEA:安裝「Database Tools」插件,連接MySQL后,在「Database」面板中找到「Slow Queries」,即可查看慢查詢?nèi)罩荆?/p>

• phpMyAdmin:登錄后,點擊「狀態(tài)」→「慢查詢?nèi)罩尽?,即可查看和分析?/p>

• id相同:執(zhí)行順序由上到下(單表查詢、簡單多表關(guān)聯(lián));

• id不同:id值越大,執(zhí)行優(yōu)先級越高(子查詢場景,先執(zhí)行子查詢,再執(zhí)行主查詢);

• id為NULL:最后執(zhí)行(比如union查詢的匯總操作)。

• system:表中只有一行數(shù)據(jù)(系統(tǒng)表),效率最高,幾乎不會出現(xiàn);

• const:通過主鍵或唯一索引查詢,只匹配一行數(shù)據(jù),效率極高(比如 where id = 1);

• eq_ref:多表關(guān)聯(lián)時,通過主鍵或唯一索引關(guān)聯(lián),每行數(shù)據(jù)只匹配一行關(guān)聯(lián)數(shù)據(jù)(比如user表和order表,通過user.id=order.user_id關(guān)聯(lián),user.id是主鍵);

• ref:通過普通索引查詢,匹配多行數(shù)據(jù)(比如 where category_id = 10,category_id是普通索引);

• range:通過索引范圍查詢(比如 where id > 10、where age between 20 and 30),效率比ref略低;

• index:全索引掃描(掃描整個索引表,不掃描數(shù)據(jù)),效率較低;

• ALL:全表掃描(掃描整個表的數(shù)據(jù)),效率最低,慢查詢的主要原因之一。

• Using index:使用了覆蓋索引(查詢的字段都在索引中,無需回表查詢數(shù)據(jù)),效率極高,是優(yōu)化的目標;

• Using where:使用了where條件過濾數(shù)據(jù),過濾效果較好;

• Using index condition:使用了索引條件推送(ICP),減少回表查詢的次數(shù),提升效率。

• Using filesort:無法使用索引排序,需要在磁盤或內(nèi)存中進行排序(文件排序),耗時極長,尤其是數(shù)據(jù)量大時;

• Using temporary:需要創(chuàng)建臨時表存儲查詢結(jié)果,再進行后續(xù)操作(比如group by、distinct、union),耗時較長;

• Using join buffer:多表關(guān)聯(lián)時,沒有使用索引,需要使用連接緩沖區(qū)存儲關(guān)聯(lián)數(shù)據(jù),效率低;

• Using where; Using filesort:使用了where過濾,但排序沒有使用索引,需要優(yōu)化排序字段的索引;

• Using where; Using temporary; Using filesort:最糟糕的情況,需要創(chuàng)建臨時表、進行文件排序,必須優(yōu)先優(yōu)化。

• type = ALL(全表掃描)

• possible_keys = idx_category_id

• key = NULL(未使用任何索引)

• rows = 800000(預(yù)估掃描80萬行)

• Extra = Using where(只使用where過濾,無索引)

• type = range(索引范圍查詢)

• possible_keys = idx_category_price

• key = idx_category_price(實際使用聯(lián)合索引)

• rows = 5000(預(yù)估掃描5000行,大幅減少)

• Extra = Using index condition; Using where(使用索引條件推送,過濾效果好)

1. 判斷是否走索引:看 type 字段(是否為const、ref、range)和 key 字段(是否為非NULL);如果key為NULL、type為ALL,說明未走索引;

2. 判斷慢SQL原因:結(jié)合 rows(掃描行數(shù))、Extra(是否有filesort、temporary)、type 字段,比如:rows過大說明掃描行數(shù)多,Extra出現(xiàn)filesort說明排序無索引,type為ALL說明全表掃描。

• 平衡樹結(jié)構(gòu),查詢效率穩(wěn)定:B+樹的高度固定(一般為3-4層),無論查詢哪個數(shù)據(jù),都只需要3-4次磁盤IO操作,耗時穩(wěn)定(磁盤IO是MySQL性能瓶頸,減少IO次數(shù)就是提升性能)。比如單表數(shù)據(jù)量千萬級時,B+樹高度僅為4層,查詢耗時可控制在10ms以內(nèi)。

• 葉子節(jié)點有序且相連,支持范圍查詢:B+樹的所有葉子節(jié)點按順序排列,且葉子節(jié)點之間通過指針相連,這也是“range查詢”(如id>10、age between 20 and 30)高效的核心原因——MySQL只需找到范圍的起始葉子節(jié)點,就能通過指針遍歷所有符合條件的節(jié)點,無需回表掃描整個索引。

• 非葉子節(jié)點只存索引鍵,葉子節(jié)點存完整數(shù)據(jù)(聚簇索引):這是InnoDB索引與MyISAM索引的核心區(qū)別,也是理解“回表查詢”“覆蓋索引”的關(guān)鍵,后面會詳細拆解。

• 索引鍵有序,支持排序優(yōu)化:B+樹的索引鍵是有序存儲的,因此當SQL中包含order by、group by時,如果排序字段與索引鍵一致,MySQL可以直接利用索引的有序性完成排序,避免出現(xiàn)“Using filesort”(文件排序),大幅提升排序效率。

• 索引鍵:默認使用**主鍵(primary key)**作為索引鍵;如果表沒有主鍵,MySQL會自動選擇一個唯一非空字段作為聚簇索引;如果沒有唯一非空字段,MySQL會自動生成一個隱藏的row_id作為聚簇索引。

• 存儲結(jié)構(gòu):B+樹的非葉子節(jié)點存儲主鍵值,葉子節(jié)點存儲整個行的數(shù)據(jù)(而非指針)。也就是說,聚簇索引的葉子節(jié)點就是表的實際數(shù)據(jù)行,索引與數(shù)據(jù)是“聚簇”在一起的。

• 查詢效率:通過聚簇索引查詢時,找到葉子節(jié)點就直接獲取到了完整行數(shù)據(jù),無需回表,效率極高(type可達const級別)。

• 索引鍵:可以是任意字段(或字段組合),如category_id、create_time、(name, age)等。

• 存儲結(jié)構(gòu):B+樹的非葉子節(jié)點存儲非聚簇索引的鍵值,葉子節(jié)點不存儲完整行數(shù)據(jù),只存儲聚簇索引的鍵值(主鍵值)

• 查詢流程(重點!回表查詢的根源):通過非聚簇索引查詢時,首先找到葉子節(jié)點中的主鍵值,然后再通過聚簇索引(主鍵索引)查找對應(yīng)的葉子節(jié)點,才能獲取到完整的行數(shù)據(jù)——這個“通過非聚簇索引找到主鍵,再通過聚簇索引找數(shù)據(jù)”的過程,就是回表查詢

1. 通過非聚簇索引(idx_category_id)的B+樹,找到所有category_id=10的葉子節(jié)點,獲取對應(yīng)的主鍵值(id);

2. 再通過聚簇索引(主鍵id)的B+樹,根據(jù)主鍵值找到對應(yīng)的葉子節(jié)點,獲取完整的商品數(shù)據(jù);

3. 將所有符合條件的商品數(shù)據(jù)匯總,返回給客戶端。

1. 存儲內(nèi)容:聚簇索引葉子節(jié)點存完整行數(shù)據(jù),非聚簇索引葉子節(jié)點存主鍵值;

2. 數(shù)量限制:聚簇索引每個表只能有一個,非聚簇索引可以有多個;

3. 查詢效率:聚簇索引無需回表,效率更高;非聚簇索引需回表,效率較低;

4. 索引鍵:聚簇索引默認用主鍵,非聚簇索引可自定義字段。

• 如果沒有覆蓋索引:需要先通過idx_category_id找到所有category_id=10的主鍵id(約5000條),再通過聚簇索引逐一回表查詢5000條數(shù)據(jù),共產(chǎn)生5001次磁盤IO(1次找主鍵,5000次回表),執(zhí)行耗時約500ms;

• 如果有覆蓋索引:無需回表,直接從非聚簇索引中獲取所有需要的字段,僅需1次磁盤IO,執(zhí)行耗時可降至20ms以內(nèi)。

• 慢查詢語句:select id, name, price from product where category_id = 10(當前索引:idx_category_id,僅包含category_id);

• 問題:查詢需要id、name、price三個字段,idx_category_id僅包含category_id,葉子節(jié)點只存主鍵id,因此需要回表查詢name和price字段,耗時約500ms;

• 優(yōu)化方案:創(chuàng)建聯(lián)合索引idx_category_name_price(category_id, name, price),該索引包含了查詢所需的所有字段(category_id用于過濾,name、price用于返回結(jié)果);

• 優(yōu)化后:通過該聯(lián)合索引查詢時,葉子節(jié)點包含category_id、name、price、主鍵id,無需回表,執(zhí)行耗時降至20ms以內(nèi),Explain的Extra字段會顯示“Using index”(標識使用了覆蓋索引)。

• 避免濫用select *select *會查詢表中所有字段,幾乎不可能使用覆蓋索引(除非索引包含所有字段,這會導(dǎo)致索引過大),因此盡量只查詢需要的字段;

• 聯(lián)合索引的字段順序:將查詢條件中的過濾字段(where后面的字段)放在聯(lián)合索引的前面,查詢所需的返回字段放在后面,既保證能命中索引,又能實現(xiàn)覆蓋;

• 避免索引過大:覆蓋索引雖好,但不能包含過多字段(尤其是大字段,如varchar(255)、text),否則會導(dǎo)致索引體積過大,增加磁盤占用和寫入開銷(insert/update/delete時需要維護索引)。

• 索引鍵無法有序匹配:B+樹索引的查詢依賴于索引鍵的有序性,如果SQL寫法破壞了索引鍵的有序性(如函數(shù)操作、運算、左模糊查詢),MySQL無法通過索引鍵快速定位數(shù)據(jù),只能放棄索引,走全表掃描;

• 索引過濾性太差:低區(qū)分度字段(如status、gender)的索引,無法有效過濾數(shù)據(jù),MySQL優(yōu)化器判斷“使用索引的開銷(回表、索引掃描)大于全表掃描的開銷”,會放棄使用索引;

• 聯(lián)合索引不滿足最左前綴匹配:聯(lián)合索引的B+樹,是按“最左前綴”的順序構(gòu)建的,若查詢條件不包含最左前綴字段,無法命中索引(后面會詳細拆解)。

以上就是SpringBoot數(shù)據(jù)庫索引優(yōu)化指南的詳細內(nèi)容,更多關(guān)于SpringBoot數(shù)據(jù)庫索引優(yōu)化的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java中獲取xml文件的某個配置節(jié)點內(nèi)容方式

    java中獲取xml文件的某個配置節(jié)點內(nèi)容方式

    這篇文章主要介紹了java中獲取xml文件的某個配置節(jié)點內(nèi)容方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Java的幾種文件拷貝方式示例詳解

    Java的幾種文件拷貝方式示例詳解

    在Java編程中文件操作是常見且重要的任務(wù)之一,其中文件拷貝是一種基本操作,這篇文章主要給大家介紹了關(guān)于Java幾種文件拷貝方式的相關(guān)資料,文中給出了詳細的代碼示例,需要的朋友可以參考下
    2024-08-08
  • Java中的MapStruct的使用方法代碼實例

    Java中的MapStruct的使用方法代碼實例

    這篇文章主要介紹了Java中的MapStruct的使用方法代碼實例,mapstruct是一種實體類映射框架,能夠通過Java注解將一個實體類的屬性安全地賦值給另一個實體類,有了mapstruct,只需要定義一個映射器接口,聲明需要映射的方法,需要的朋友可以參考下
    2023-10-10
  • spring boot集成pagehelper(兩種方式)

    spring boot集成pagehelper(兩種方式)

    這篇文章主要介紹了spring boot集成pagehelper(兩種方式),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • Intellij Idea新建SpringBoot項目方式

    Intellij Idea新建SpringBoot項目方式

    這篇文章主要介紹了Intellij Idea新建SpringBoot項目方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-09-09
  • SpringBoot中處理JSON日期格式方式

    SpringBoot中處理JSON日期格式方式

    SpringBoot中處理JSON日期格式主要有三種方式:使用@JsonFormat注解、配置默認格式以及自定義Jackson的ObjectMapper,每種方式都有其適用場景,可以根據(jù)具體需求選擇合適的方法
    2025-02-02
  • Spring底層原理深入分析

    Spring底層原理深入分析

    Spring框架是一個開放源代碼的J2EE應(yīng)用程序框架,由Rod Johnson發(fā)起,是針對bean的生命周期進行管理的輕量級容器(lightweight container)。 Spring解決了開發(fā)者在J2EE開發(fā)中遇到的許多常見的問題,提供了功能強大IOC、AOP及Web MVC等功能
    2022-07-07
  • java設(shè)計模式筆記之裝飾模式

    java設(shè)計模式筆記之裝飾模式

    這篇文章主要為大家詳細介紹了java設(shè)計模式筆記之裝飾模式,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-04-04
  • Java實現(xiàn)計算圖中兩個頂點的所有路徑

    Java實現(xiàn)計算圖中兩個頂點的所有路徑

    這篇文章主要為大家詳細介紹了如何利用Java語言實現(xiàn)計算圖中兩個頂點的所有路徑功能,文中通過示例詳細講解了實現(xiàn)的方法,需要的可以參考一下
    2022-10-10
  • Java數(shù)據(jù)結(jié)構(gòu)之加權(quán)無向圖的設(shè)計實現(xiàn)

    Java數(shù)據(jù)結(jié)構(gòu)之加權(quán)無向圖的設(shè)計實現(xiàn)

    加權(quán)無向圖是一種為每條邊關(guān)聯(lián)一個權(quán)重值或是成本的圖模型。這種圖能夠自然地表示許多應(yīng)用。這篇文章主要介紹了加權(quán)無向圖的設(shè)計與實現(xiàn),感興趣的可以了解一下
    2022-11-11

最新評論

双柏县| 民县| 嫩江县| 桑植县| 鄱阳县| 兴城市| 攀枝花市| 郁南县| 临夏市| 新密市| 贵定县| 澄江县| 马边| 上犹县| 义马市| 武汉市| 浮梁县| 枣庄市| 雅安市| 新郑市| 大余县| 兴义市| 华安县| 水富县| 潜江市| 曲周县| 乳源| 土默特右旗| 光泽县| 望都县| 葵青区| 德江县| 休宁县| 轮台县| 墨江| 曲周县| 阳西县| 闽侯县| 龙井市| 礼泉县| 教育|