MySQL SQL 優化:從 INSERT 到 LIMIT 的實戰與原理

在數據庫驅動的現代應用中,SQL 查詢的性能直接決定了用戶體驗和系統效率。本文將深入探討 MySQL (特別是 InnoDB 存儲引擎)中常見的 SQL 性能瓶頸,并結合實際案例,詳細剖析從數據插入到復雜分頁查詢的優化策略與底層實現原理。

我們將覆蓋以下核心優化點:

  1. INSERT 語句優化:數據寫入的基石與效率提升
  2. 主鍵優化:聚簇索引的秘密與設計哲學
  3. ORDER BY 語句優化:告別文件排序的藝術
  4. GROUP BY 分組操作優化:高效聚合與索引利用
  5. LIMIT 分頁操作優化:大偏移量分頁的挑戰與突破

貫穿始終的優化理念是:透徹理解數據庫的底層機制,盡可能利用索引、減少不必要的磁盤 I/O(特別是隨機 I/O),并避免昂貴的臨時表操作,從而“引導”數據庫走上最高效的執行路徑。


1. INSERT 語句優化:數據寫入的基石與效率提升

數據插入是所有數據庫操作的起點,其性能不僅影響數據錄入的效率,也間接影響后續查詢的性能。理解 INSERT 的底層行為是優化其性能的關鍵。

1.1 INSERT 的底層原理

在 InnoDB 存儲引擎中,INSERT 操作遠不止簡單地將數據寫入表文件。它涉及多方面的系統資源消耗:

  • 數據頁寫入與頁分裂:新數據需要存儲到聚簇索引(主鍵)的葉子節點中。InnoDB 的數據是按數據頁(通常為 16KB)為單位進行存儲和管理的,并盡可能保持主鍵的物理順序。

    • 當一個數據頁已滿,而新的數據需要插入到該頁的中間位置時(例如,使用非自增主鍵,導致新數據的主鍵值可能落在已有數據范圍之間),InnoDB 就必須進行頁分裂(Page Split)
    • 頁分裂現象:想象一個裝滿文件的文件夾。當你想在中間插入一份新文件,但文件夾已滿時,你必須把一部分文件挪到另一個新的空文件夾里,才能給新文件騰出空間。在數據庫中,這意味著 InnoDB 會將這個已滿的數據頁中的一部分數據移動到一個新的數據頁上,同時更新相關索引樹中指向這些頁的指針。
    • 頁分裂開銷:頁分裂會帶來額外的磁盤 I/O 操作(需要寫入新的數據頁,并可能需要寫入父節點頁來更新指針)和 CPU 消耗。更重要的是,頻繁的頁分裂可能導致數據在磁盤上變得不連續,產生碎片。這會增加后續查詢時的隨機 I/O,因為讀取原本連續的數據現在可能需要跳到多個不連續的磁盤位置,從而降低查詢性能。
  • 二級索引維護:表中的每一個二級索引(輔助索引)也需要插入新的索引條目。每個二級索引條目通常包含 (索引列值, 主鍵值)。這個過程同樣可能導致索引頁分裂,帶來隨機 I/O。

  • Redo Log(重做日志)寫入:為保證事務的持久性和崩潰恢復,所有數據修改操作(包括數據寫入和索引更新)都會記錄到 Redo Log。Redo Log 會先寫入內存中的 redo log buffer,然后根據配置刷新到磁盤的 redo log file。頻繁的磁盤刷新是主要的 I/O 瓶頸。

  • Undo Log(回滾日志)寫入:為實現事務的原子性和隔離性,INSERT 操作也會生成 Undo Log,用于事務回滾和MVCC(多版本并發控制)。

  • 鎖競爭:在并發環境下,插入操作需要獲取行鎖(或更高級別的鎖),維護鎖的獲取和釋放也有開銷。

  • 網絡通信:客戶端與服務器之間的 SQL 語句傳輸和結果返回。

  • SQL 解析與優化:服務器端對接收到的 SQL 語句進行解析、校驗和生成執行計劃。

?

1.2 優化策略與實戰

核心思想:通過減少交互次數、降低 I/O 頻率和避免不必要的開銷,將零散的隨機寫入轉化為更集中的順序寫入。

我們以一個電商平臺的訂單系統為例,假設需要向 orders 表插入數據。

CREATE TABLE orders (order_id BIGINT PRIMARY KEY AUTO_INCREMENT,user_id INT NOT NULL,product_id INT NOT NULL,quantity INT NOT NULL,order_time DATETIME DEFAULT CURRENT_TIMESTAMP,status VARCHAR(50) DEFAULT 'pending',INDEX idx_user_id (user_id),INDEX idx_order_time (order_time)
) ENGINE=InnoDB;
優化方案 1:批量插入 (Batch Insert)

場景:用戶下單后,后臺系統需要將大量訂單信息寫入數據庫,例如有 1000 個用戶幾乎同時下單。

優化前:單條插入

-- 優化前:1000 條獨立的 INSERT 語句
INSERT INTO orders (user_id, product_id, quantity) VALUES (101, 2001, 1);
INSERT INTO orders (user_id, product_id, quantity) VALUES (102, 2002, 2);
-- ... 重復 998 次 ...
INSERT INTO orders (user_id, product_id, quantity) VALUES (1100, 2100, 3);

底層原理分析(單條插入)

  • 網絡通信開銷(高):每條 INSERT 語句都需要一次獨立的網絡往返(Round Trip Time, RTT)。1000 條語句意味著 1000 次網絡請求和響應,每次 RTT 都包含 TCP/IP 協議棧的握手、數據傳輸、確認等開銷。這就像每次只拿一件商品,然后跑到收銀臺結賬,再跑回去拿下一件。
  • SQL 解析和優化開銷(高):MySQL 服務器每次收到 SQL 語句都需要進行語法解析、語義檢查、權限驗證,并生成執行計劃。1000 條語句就需要重復 1000 次這個過程。
  • 事務提交開銷(高,默認 autocommit=1:如果 autocommit 處于 ON 狀態(MySQL 默認),每條 INSERT 語句都是一個獨立的事務。這意味著每次插入后,都需要執行一次 COMMIT 操作。COMMIT 操作會強制將當前事務的所有 Redo Log 數據刷新到磁盤(特別是當 innodb_flush_log_at_trx_commit=1 時),并釋放事務所持有的鎖。這好比每取一件商品就立即跑到收銀臺結賬,然后回到貨架再取下一件。
  • I/O 開銷(高):頻繁的事務提交導致 Redo Log 頻繁刷盤,增加磁盤 I/O。雖然 InnoDB 有緩沖池,但每次提交都可能強制將部分臟頁刷到磁盤,影響性能。

優化后:批量插入

-- 優化后:一條批量 INSERT 語句
INSERT INTO orders (user_id, product_id, quantity) VALUES
(101, 2001, 1),
(102, 2002, 2),
(103, 2003, 1),
-- ... 更多行 ...
(1100, 2100, 3);

底層原理分析(批量插入)

  • 網絡通信開銷(極低):1000 條數據只通過一次網絡請求發送到 MySQL 服務器,極大地減少了網絡往返次數。這就像你一次性拿了所有商品,然后一次性跑到收銀臺結賬。
  • SQL 解析和優化開銷(低):MySQL 服務器只對一條 SQL 語句進行解析和優化,然后一次性處理所有數據。
  • 事務提交開銷(極低):所有插入操作都在一個事務中完成(因為是單條 SQL 語句),只需進行一次 COMMIT 操作。這大大減少了 Redo Log 的刷新頻率和鎖的釋放頻率。
  • I/O 開銷(低):由于事務提交次數減少,Redo Log 刷盤次數減少。并且,InnoDB 在一次大批量插入時,可以更好地利用其內部優化,例如將數據和索引頁的修改聚集起來,減少隨機 I/O,更有效地利用緩沖池。對于自增主鍵,數據通常是順序寫入,可以充分利用磁盤的順序寫入優勢。
  • 鎖開銷(低):雖然仍會獲取行鎖,但在同一個事務中,鎖的獲取和釋放更集中高效。

總結:批量插入的核心在于減少了單位數據量的操作次數,包括網絡通信、SQL 解析、事務提交和磁盤 I/O,從而顯著提高吞吐量。

優化方案 2:使用 LOAD DATA INFILE

場景:每晚凌晨,需要將當天所有從上游系統同步過來的交易流水(CSV 文件)導入到 daily_transactions 表中,文件可能有幾百萬甚至上億行。

CREATE TABLE daily_transactions (transaction_id BIGINT PRIMARY KEY AUTO_INCREMENT,order_id BIGINT NOT NULL,amount DECIMAL(10, 2) NOT NULL,transaction_time DATETIME NOT NULL,payment_method VARCHAR(50),INDEX idx_order_id (order_id),INDEX idx_transaction_time (transaction_time)
) ENGINE=InnoDB;

假設 CSV 文件 transactions.csv 內容如下:

1001,10.50,2025-06-07 10:00:00,Alipay
1002,25.00,2025-06-07 10:05:00,WeChatPay
1003,150.75,2025-06-07 10:10:00,CreditCard
... (幾百萬行)

優化前:分批批量插入(通過應用程序)

即使使用批量插入,當文件有幾百萬行時,將其全部拼接成一個巨大的 INSERT 語句是不現實的(SQL 語句長度限制、內存限制)。通常會寫程序讀取文件,然后每隔幾千或幾萬行組成一個批量 INSERT 語句提交。

底層原理分析(分批批量插入)

雖然比單條插入好很多,但仍然存在:

  • 網絡通信開銷:每 batch_size 行數據就需要一次網絡往返。幾百萬行數據仍然需要幾百次到幾千次網絡往返。
  • SQL 解析和優化開銷:每次批次都需要進行 SQL 解析和優化。
  • 應用程序邏輯開銷:應用程序需要讀取文件、解析數據、構建 SQL 字符串。

優化后:使用 LOAD DATA INFILE

-- 優化后:直接從文件導入
LOAD DATA INFILE '/path/to/your/transactions.csv'
INTO TABLE daily_transactions
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
(order_id, amount, transaction_time, payment_method);

底層原理分析(LOAD DATA INFILE

  • 極低的網絡通信開銷:客戶端只需發送一條 LOAD DATA 命令。數據文件(如果服務器有權限訪問)直接由 MySQL 服務器讀取并處理。如果是 LOCAL 選項,文件由客戶端讀取并一次性發送給服務器,通常不需要客戶端程序進行多次數據傳輸。這就像你直接把整個裝滿商品的推車推到收銀臺,由收銀員(MySQL 服務器)自己清點和處理。
  • 跳過 SQL 解析和優化LOAD DATA INFILE 不是標準的 DML 語句,MySQL 有專門的內部接口來處理它。它直接讀取文件內容并映射到表字段,省去了 SQL 解析和執行計劃生成的復雜過程。
  • 高效的數據寫入:MySQL 的存儲引擎(特別是 InnoDB)對 LOAD DATA INFILE 有專門的優化:
    • 批量構建數據頁:它可以在內存中批量構建數據頁和索引頁,減少零散的隨機 I/O,將多次修改聚合成更集中的寫入。
    • 延遲刷新:可以更長時間地將數據緩存在內存中,減少強制刷盤次數。
    • 更有效的日志寫入:內部機制會更高效地批量寫入 Redo Log 和 Undo Log。
    • 鎖機制優化:InnoDB 在 LOAD DATA INFILE 時通常會采取更高效的鎖策略,例如在某些情況下可能會在內部減少行鎖的粒度或避免頻繁加鎖解鎖。
  • 服務器端處理:整個文件讀取和數據導入過程都在 MySQL 服務器端進行(如果文件在服務器上),或者由客戶端一次性發送文件內容給服務器,大大減少了客戶端的負擔。

總結LOAD DATA INFILE 是 MySQL 專門為大批量數據導入設計的高效工具。它通過繞過標準的 SQL 解析/優化流程、減少網絡往返、以及內部的存儲引擎優化(批量寫入、延遲刷新),實現了遠超普通 INSERT 語句的性能。

優化方案 3:關閉自動提交 (Disable Autocommit)

場景:一個復雜的業務流程,需要在一系列數據庫操作(包括多個 INSERT, UPDATE, DELETE)完成后才一起提交,以保證數據的一致性。

優化前:默認自動提交 (autocommit=1)

-- 假設默認 autocommit=1
INSERT INTO orders (user_id, product_id, quantity) VALUES (101, 2001, 1); -- 事務 1 提交
INSERT INTO orders (user_id, product_id, quantity) VALUES (102, 2002, 2); -- 事務 2 提交
-- ... (其他操作,例如更新用戶積分)
UPDATE users SET points = points + 10 WHERE user_id = 101; -- 事務 3 提交

底層原理分析(默認自動提交)

  • 頻繁的事務提交:每條 DML 語句都作為一個獨立的事務,執行完成后立即提交。
  • 高 Redo Log 刷盤頻率:如果 innodb_flush_log_at_trx_commit = 1(默認值),每次 COMMIT 都會強制將 Redo Log buffer 中的內容刷新到磁盤上的 Redo Log 文件。這是非常重的磁盤 I/O 操作,且會阻塞當前事務直到數據真正寫入磁盤。這就像每次你完成一個小任務,就必須立刻跑到總辦公室去匯報并簽字確認,然后再回來做下一個小任務。
  • 頻繁的鎖釋放:每個事務提交后,其持有的所有鎖都會被釋放。雖然行鎖粒度低,但頻繁的獲取和釋放仍然有開銷。
  • 數據一致性風險:如果中間某個操作失敗,之前已提交的操作無法回滾,可能導致數據不一致。

優化后:手動管理事務 (SET autocommit = 0; ... COMMIT;)

SET autocommit = 0; -- 關閉自動提交
INSERT INTO orders (user_id, product_id, quantity) VALUES (101, 2001, 1);
INSERT INTO orders (user_id, product_id, quantity) VALUES (102, 2002, 2);
-- ... (其他操作,例如更新用戶積分)
UPDATE users SET points = points + 10 WHERE user_id = 101;
COMMIT; -- 統一提交所有操作
SET autocommit = 1; -- 恢復自動提交(可選,根據業務需求)

底層原理分析(手動管理事務)

  • 減少事務提交次數:所有操作在一個事務中完成,最后只提交一次。
  • 降低 Redo Log 刷盤頻率COMMIT 操作只在事務結束時執行一次,Redo Log 只在此時被強制刷新到磁盤。事務進行期間,Redo Log 數據會累積在內存的 redo log buffer 中,只有當 buffer 滿或定期檢查時才寫入文件,從而大大減少了磁盤 I/O。這好比你可以連續完成多個小任務,然后一次性跑到總辦公室去匯報并簽字確認。
  • 更少的鎖開銷:事務期間,行鎖會被持有,但在事務結束時才統一釋放。雖然持有時間可能變長,但避免了頻繁的獲取和釋放,減少了鎖管理的開銷。
  • 保證數據一致性:所有操作作為一個原子單元,要么全部成功,要么全部回滾,確保了業務邏輯的完整性。

總結:手動管理事務通過將多個操作打包成一個邏輯單元,減少了事務提交的頻率,從而降低了 Redo Log 刷盤 I/O 和鎖操作開銷,同時保證了數據的一致性。

優化方案 4:調整 InnoDB 配置參數 (innodb_flush_log_at_trx_commit)

場景:大量數據導入的 ETL 過程,對數據一致性有一定容忍度(例如,即使 MySQL 崩潰,丟失最后 1 秒的數據也問題不大,因為可以重新導入)。

優化前:默認配置 (innodb_flush_log_at_trx_commit = 1)

這是 MySQL 默認設置,提供了最高的持久性(數據安全性)。

-- innodb_flush_log_at_trx_commit = 1 (默認)
INSERT INTO orders (user_id, product_id, quantity) VALUES (101, 2001, 1); -- 提交時,redo log 刷新到磁盤
INSERT INTO orders (user_id, product_id, quantity) VALUES (102, 2002, 2); -- 提交時,redo log 刷新到磁盤

底層原理分析(innodb_flush_log_at_trx_commit = 1

  • 高持久性:每次事務提交,InnoDB 都會將 Redo Log buffer 中的數據寫入到日志文件(ib_logfile),并強制操作系統將日志文件刷寫到磁盤(fsync)。這確保了即使操作系統崩潰,已提交的事務數據也不會丟失。
  • 頻繁的磁盤 I/Ofsync 是一個相對耗時的操作,它會阻塞當前事務直到數據真正寫入磁盤。對于高并發的 INSERT 操作,這會成為主要的性能瓶頸。

優化后:調整參數 (innodb_flush_log_at_trx_commit = 0= 2)

-- 暫時設置參數以優化插入性能
SET GLOBAL innodb_flush_log_at_trx_commit = 2; -- 或者 = 0
-- 或者直接修改 my.cnf 配置文件INSERT INTO orders (user_id, product_id, quantity) VALUES (101, 2001, 1);
INSERT INTO orders (user_id, product_id, quantity) VALUES (102, 2002, 2);
-- ... 大量插入操作 ...-- 插入完成后可以考慮恢復默認值以保證數據安全性
-- SET GLOBAL innodb_flush_log_at_trx_commit = 1;

底層原理分析(innodb_flush_log_at_trx_commit = 0= 2

  • innodb_flush_log_at_trx_commit = 0
    • 日志寫入:事務提交時,Redo Log buffer 內容寫入日志文件,但不會立即刷新到磁盤。
    • 磁盤刷新:日志文件每秒鐘被刷新到磁盤一次。
    • 性能:最高。因為磁盤 I/O 操作大大減少,寫入是異步進行的。
    • 風險:MySQL 服務器崩潰時,最多可能丟失 1 秒的已提交事務數據。
  • innodb_flush_log_at_trx_commit = 2
    • 日志寫入:事務提交時,Redo Log buffer 內容寫入日志文件,但不會立即刷新到磁盤。
    • 磁盤刷新:日志文件也是每秒鐘被刷新到磁盤一次。
    • 性能:次高。比 =0 更安全一點,因為數據已經寫入操作系統的文件緩存,MySQL 服務器崩潰時數據不丟失。
    • 風險操作系統崩潰時,最多可能丟失 1 秒的已提交事務數據。

總結:調整 innodb_flush_log_at_trx_commit 參數是通過犧牲一部分數據持久性(可靠性)來換取更高的寫入性能。其底層原理是減少了強制的 fsync 磁盤 I/O 操作,讓 Redo Log 的刷新更加異步和批量化,從而降低了事務提交時的延遲。

優化方案 5:精簡索引設計

場景:隨著業務發展,對 orders 表增加了多個查詢需求,可能導致創建了過多索引。

優化前:過多不必要的索引

假設除了主鍵、idx_user_ididx_order_time 外,又額外創建了一個不常用的索引:

CREATE TABLE orders (order_id BIGINT PRIMARY KEY AUTO_INCREMENT,user_id INT NOT NULL,product_id INT NOT NULL,quantity INT NOT NULL,order_time DATETIME DEFAULT CURRENT_TIMESTAMP,status VARCHAR(50) DEFAULT 'pending',INDEX idx_user_id (user_id),INDEX idx_order_time (order_time),INDEX idx_product_status (product_id, status) -- 假設這個索引很少用于查詢,但在插入時會增加開銷
) ENGINE=InnoDB;

底層原理分析(過多索引)

  • 索引維護開銷:每次 INSERT 操作不僅要將數據寫入數據頁,還要更新所有相關的索引。對于每個非聚簇索引(二級索引),MySQL 需要:
    • 找到索引頁的正確位置。
    • 插入新的索引條目。
    • 如果索引頁已滿,可能導致頁分裂,需要額外的 I/O 操作來分配新頁并移動數據。
    • 所有這些操作都需要記錄到 Redo Log 和 Undo Log 中。
  • 隨機 I/O:索引通常是 B+樹結構,其節點可能分散在磁盤的各個位置。插入新的索引條目往往會導致隨機 I/O,效率低于順序 I/O。
  • 內存開銷:索引數據也需要占用 InnoDB 緩沖池的內存。過多的索引會減少緩沖池中可用作數據緩存的空間,導致熱點數據被擠出內存,從而增加查詢時的磁盤 I/O。
  • 鎖爭用:在多并發插入時,每個索引的更新都可能涉及到鎖,索引數量減少可以降低鎖爭用。

優化后:精簡索引

經過分析,發現 idx_product_status 索引利用率很低,決定刪除它。

-- 優化后:刪除不必要的索引
DROP INDEX idx_product_status ON orders;-- 插入操作現在將不再維護這個索引
INSERT INTO orders (user_id, product_id, quantity) VALUES (101, 2001, 1);

底層原理分析(精簡索引)

  • 減少索引維護:每次 INSERT 操作需要更新的索引樹數量減少。直接消除了維護 idx_product_status 索引的額外開銷。
  • 降低 I/O:減少了不必要的索引頁查找、更新和可能的頁分裂。
  • 減少鎖爭用:更少的索引意味著更少的鎖操作,降低了并發插入時的鎖爭用。
  • 提高緩沖池效率:更少的索引數據需要緩存,可以為實際的數據頁或更常用的索引頁騰出更多緩沖池空間,提升整體緩存命中率。

總結:索引在查詢時能顯著提高性能,但在寫入時會增加額外開銷。優化 INSERT 的原則是只創建真正必要且高效的索引。其底層原理是減少了索引維護的 CPU、內存和磁盤 I/O 開銷,尤其是隨機 I/O。


2. 主鍵優化:聚簇索引的秘密與設計哲學

主鍵在 InnoDB 存儲引擎中扮演著核心角色,它的設計不僅僅影響 INSERT 性能,更對所有查詢的性能(特別是二級索引的回表操作)產生深遠影響。

2.1 主鍵的物理特性:聚簇索引
  • InnoDB 的數據行是根據主鍵的順序物理存儲在磁盤上的,這種存儲方式稱為聚簇索引。這意味著,當數據按主鍵順序寫入時,會高效地追加到磁盤上。
  • 每個二級索引的葉子節點存儲的不是完整的行數據,而是 (索引列值, 主鍵值) 對。當通過二級索引查詢時,MySQL 會先從二級索引找到對應行的主鍵值,然后利用這個主鍵值到聚簇索引中定位并獲取完整的行數據,這個過程就是回表
2.2 優化策略:選擇短小、順序、非空的主鍵

核心思想:優化主鍵就是優化聚簇索引和二級索引的存儲與 I/O。

  • 選擇自增主鍵 (AUTO_INCREMENT)
    • 原理:自增整數主鍵保證了新數據總是追加到聚簇索引的最后一個數據頁的末尾。這種寫入方式是順序寫入,極大減少了頁分裂和隨機 I/O,INSERT 效率最高。
    • 對二級索引的影響:自增主鍵也優化了二級索引的寫入。因為二級索引存儲的是 (索引列值, 主鍵值),當主鍵是順序遞增時,即使索引列值是隨機的,每次插入的主鍵值也相對遞增。這使得二級索引的葉子節點在分裂時,可以更好地利用空間,減少碎片,從而減少索引維護的 I/O。
    • 反例:如果使用 UUID 或業務無關的隨機字符串作為主鍵,新數據在聚簇索引中將是隨機分布的。這會導致頻繁的頁分裂和大量的隨機 I/O,嚴重影響 INSERT 性能。同時,由于 UUID 較長且無序,二級索引會變得更大且更不緊湊,查詢性能也會受到影響。
  • 短小的主鍵
    • 原理:主鍵越短,每個二級索引存儲的主鍵值就越小。這樣,在相同的二級索引頁大小下,可以存儲更多的索引條目,從而減少二級索引的高度,使得遍歷二級索引時需要讀取的磁盤頁數量減少,最終減少了 I/O。
    • 反例:如果主鍵是長字符串,二級索引的葉子節點會存儲更多的冗余數據,導致索引樹更深、更龐大,查詢效率下降。
  • 非空主鍵:主鍵列必須是 NOT NULL,且唯一。這是數據庫的基本要求,也保證了索引的完整性。

總結:理想的主鍵是自增的整數類型。它同時滿足了短小、順序、非空的優點,是 MySQL InnoDB 高性能的基石。


3. ORDER BY 語句優化:告別文件排序的藝術

ORDER BY 排序操作是 SQL 查詢的常見性能瓶頸,尤其當數據量較大時,可能導致昂貴的“文件排序”(Filesort)。

3.1 ORDER BY 的底層原理
  • 文件排序 (Filesort)
    • 原理:當 MySQL 無法利用索引直接提供排序結果時,它會將被查詢的數據(通常是經過 WHERE 條件過濾后的結果集)加載到內存中的一個排序緩沖區(sort_buffer_size)進行排序。如果數據量超過 sort_buffer_size,MySQL 會將數據分批寫入磁盤上的臨時文件,然后使用多路歸并排序算法進行合并排序。
    • 開銷Filesort 涉及大量的 CPU 運算(排序算法),以及潛在的磁盤 I/O(讀寫臨時文件),這是性能瓶頸的常見來源。
    • EXPLAIN 輸出Extra 列會顯示 Using filesort
  • 索引排序
    • 原理:當 ORDER BY 子句的列與某個索引的列順序和方向完全匹配時,MySQL 可以直接遍歷該索引的葉子節點,按索引的物理順序獲取已經排好序的數據,無需額外的排序操作。
    • 開銷:幾乎沒有額外的排序開銷,I/O 主要集中在索引掃描和回表。
    • EXPLAIN 輸出Extra 列可能顯示 Using index (表示覆蓋索引) 或不顯示 filesort 相關的關鍵詞。
3.2 優化策略與實戰

核心思想:通過創建合適的索引,將外部排序轉化為索引內部的有序掃描,避免文件排序。

我們將使用 cycling_teamscyclists 表進行示例。

-- 1. 創建車隊表
CREATE TABLE cycling_teams (team_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '車隊ID',team_name VARCHAR(50) NOT NULL COMMENT '車隊名',country VARCHAR(50) NOT NULL COMMENT '所屬國家',bike_brand VARCHAR(50) NOT NULL COMMENT '使用自行車品牌',contact_number VARCHAR(20) COMMENT '聯系電話'
);-- 2. 創建騎行運動員表
CREATE TABLE cyclists (cyclist_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '運動員ID',team_id INT NOT NULL COMMENT '所屬車隊ID',name VARCHAR(50) NOT NULL COMMENT '姓名',gender VARCHAR(10) NOT NULL COMMENT '性別',age INT NOT NULL COMMENT '年齡',nationality VARCHAR(50) NOT NULL COMMENT '國籍'
);
策略 1:為 ORDER BY 列創建索引,并確保索引方向匹配

原理:為 ORDER BY 涉及的列創建索引,讓 MySQL 可以利用索引的天然有序性來滿足排序需求。在 MySQL 8.0+ 版本中,還支持在索引中指定列的降序方向(col DESC),這使得 ORDER BY col DESC 也能直接利用降序索引,從而避免文件排序。

案例 1:查詢所有車隊信息,按車隊名稱降序排列

  • 原始 SQLSELECT * FROM cycling_teams ORDER BY team_name DESC;

  • 優化前 EXPLAIN 結果

    id,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra
    1,SIMPLE,cycling_teams,,ALL,,,,,8,100,Using filesort
    
    • 分析type: ALL 表示 MySQL 對 cycling_teams 表進行了全表掃描。Extra 列的 Using filesort 明確指出,MySQL 需要額外的步驟來對數據進行排序,這通常意味著將數據加載到內存或臨時文件進行排序,開銷較大。
  • 優化思路:為了避免 Using filesort,我們需要一個能夠直接提供 team_name 降序排列的索引。一個合適的索引也能顯著減少排序開銷,不過需要確定它仍需要回表。

  • 優化后 SQL 及 EXPLAIN (MySQL 8.0+)

    1. 創建降序索引CREATE INDEX idx_team_name_desc ON cycling_teams(team_name DESC);
    2. 執行原始查詢EXPLAIN SELECT * FROM cycling_teams ORDER BY team_name DESC;
    3. EXPLAIN 結果
      id,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra
      1,SIMPLE,cycling_teams,,index,,idx_team_name_desc,52,,8,100,Using index
      
    • 原理
      • 通過創建 idx_team_name_desc 索引,MySQL 現在可以直接利用這個索引的物理順序來滿足 ORDER BY team_name DESC 的排序需求。type: index 表示 MySQL 正在遍歷整個索引樹來獲取排序后的數據,而不再是全表掃描。
      • 最關鍵的是,Extra 列中不再出現 Using filesort。這說明 MySQL 成功地避免了昂貴的外部文件排序操作。
      • 雖然 SELECT * 仍然意味著需要進行回表操作(因為 team_name 索引的葉子節點只存儲 team_name 和主鍵 team_id,不包含其他列),但相比于全表掃描并進行文件排序,利用索引進行順序掃描和回表通常是更優的選擇,因為它減少了大量的 CPU 排序開銷,并可能減少磁盤 I/O。
策略 2:WHEREORDER BY 同時利用復合索引

原理:將 WHERE 條件中用于過濾的列作為復合索引的前綴,ORDER BY 的列緊隨其后,且順序和方向匹配。這樣,MySQL 可以先通過索引過濾數據,再利用索引的有序性進行排序。

案例 2:查詢年齡大于25歲的男性運動員的id,按國籍升序、年齡降序排列

  • SQLSELECT cyclist_id FROM cyclists WHERE age > 25 AND gender = '男' ORDER BY nationality ASC, age DESC;

  • 優化前 EXPLAIN 結果

    id,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra
    1,SIMPLE,cyclists,,ALL,,,,,80,3.33,Using where; Using filesort
    
    • 分析type: ALLUsing filesort 再次表明全表掃描和文件排序的瓶頸。多條件過濾和多列排序,且排序方向不同,使得優化器無法利用現有索引。
  • 優化思路:我們需要一個復合索引,能夠同時服務 WHERE 條件和 ORDER BY 子句。

    • gender 是等值條件,適合放在索引最前面進行快速過濾。
    • nationalityORDER BY 的第一列,應緊隨 gender
    • age DESCORDER BY 的第二列,應緊隨 nationality,并指定降序。
  • 優化后索引CREATE INDEX idx_gender_nationality_age?ON cyclists(gender, nationality, age DESC);

  • 優化后 EXPLAIN 結果

    id,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra
    1,SIMPLE,cyclists,,range,idx_gender_nationality_age,idx_gender_nationality_age,xxx,,54,10,Using where; Using index
    
    • 原理
      1. gender = '男' 利用索引前綴進行 refrange 查找,快速縮小范圍。
      2. gender 固定的條件下,索引的 nationalityage DESC 部分與 ORDER BY 的順序和方向完全匹配,因此可以在索引內部直接完成排序,避免了 Using filesort
      3. cyclist_id(主鍵)包含在索引葉子節點中,避免了回表操作,進一步提升性能。
策略 3:覆蓋索引的重要性

原理:如果 SELECT 列表中的所有列、WHERE 條件中的所有列以及 ORDER BY 子句中的所有列都能在一個索引中找到,那么這個索引就是覆蓋索引。MySQL 無需訪問主表數據行,所有數據直接從索引中獲取,這大大減少了磁盤 I/O(特別是隨機 I/O)。

案例 3:查詢所有車隊中,使用“Specialized”品牌自行車的車隊名稱和聯系電話,按車隊名稱升序排列

  • SQLSELECT team_name, contact_number FROM cycling_teams WHERE bike_brand='Specialized' ORDER BY team_name;

  • 優化前 EXPLAIN 結果

    id,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra
    1,SIMPLE,cycling_teams,,ALL,,,,,8,12.5,Using where; Using filesort
    
    • 分析bike_brand 上沒有索引,導致全表掃描,并且需要對 team_name 進行文件排序。
  • 優化后索引CREATE INDEX idx_bike_brand_team_name_contact_number ON cycling_teams(bike_brand, team_name, contact_number);

  • 優化后 EXPLAIN 結果

    id,select_type,table,partitions,type,possible_keys,key,key_len,ref,rows,filtered,Extra
    1,SIMPLE,cycling_teams,,ref,idx_bike_brand_team_name_contact_number,idx_bike_brand_team_name_contact_number,202,const,2,100,Using index
    
    • 原理
      1. bike_brand='Specialized' 利用索引前綴進行 ref 查找,高效過濾。
      2. bike_brand 確定的前提下,索引的 team_name 部分天然有序,直接滿足 ORDER BY team_name避免了 Using filesort
      3. SELECT 列表中的 team_namecontact_number 都包含在這個聯合索引中,因此實現了覆蓋索引(Using index,完全避免了回表操作,性能得到極致提升。

其他 ORDER BY 優化建議

  • 避免在 ORDER BY 中使用函數或表達式:這會使得索引失效,強制進行文件排序。
  • 盡量保持 ORDER BY 中所有列的排序方向一致:如 col1 ASC, col2 ASCcol1 DESC, col2 DESC
  • 適當調整 sort_buffer_sizemax_length_for_sort_data:如果無法避免 filesort,增大這些參數有助于將排序在內存中完成,減少磁盤 I/O。但這不能解決掃描大量數據的問題。

4. GROUP BY 分組操作優化:高效聚合與索引利用

GROUP BY 操作用于將具有相同值的行分組,并對每個組執行聚合函數。其優化目標是避免創建昂貴的臨時表(Using temporary)和文件排序(Using filesort)。

4.1 GROUP BY 的底層原理

MySQL 執行 GROUP BY 主要有以下幾種內部策略:

  • 索引分組(松散索引掃描 / 緊湊索引掃描):這是 GROUP BY 最理想的優化方式,通過利用索引的有序性直接完成分組。
    • 松散索引掃描 (Loose Index Scan):當 GROUP BY 的列是某個復合索引的最左前綴,并且查詢中沒有 WHERE 條件或 WHERE 條件只涉及索引的這個最左前綴時,MySQL 可以跳過不必要的索引條目,直接在索引中找到每個分組的第一個值,然后跳到下一個分組的第一個值。它不會掃描所有符合條件的索引條目。EXPLAIN 通常顯示 Using index for group-by
    • 緊湊索引掃描 (Tight Index Scan):當 GROUP BY 的列是某個復合索引的完整前綴,或者 WHERE 條件使用了索引的最左前綴,并且 GROUP BY 剩余的索引前綴,MySQL 會連續掃描索引中的所有符合條件的條目,并利用索引的有序性進行分組。它掃描的范圍比松散索引掃描大,但仍然是高效的索引掃描。EXPLAIN 通常不顯示特定的 group-by 關鍵詞,但 typerangeindex,且沒有 Using filesortUsing temporary
  • 使用臨時表(Using Temporary Table:當無法利用索引直接完成分組時,MySQL 會創建內部臨時表來輔助 GROUP BY 操作。
    • 排序分組(Sort-based Grouping):MySQL 將符合 WHERE 條件的數據讀取出來,然后將這些數據排序,使得相同分組的行相鄰。排序完成后,再遍歷有序的數據集,進行分組和聚合計算。這個排序過程可能在內存中完成,也可能在磁盤上進行文件排序。EXPLAIN 通常顯示 Using temporaryUsing filesort
    • 哈希分組(Hash-based Grouping):MySQL 構建一個哈希表,將每一行的 GROUP BY 列作為哈希鍵,將聚合結果存儲在哈希表中。這種方式避免了排序,但消耗內存。在較新的 MySQL 版本中,哈希聚合可能不顯示 Using filesortUsing temporary
4.2 優化策略與實戰

核心思想:通過合理的索引設計,讓 GROUP BY 操作利用索引的有序性,避免昂貴的臨時表和文件排序。

策略 1:創建復合索引,并確保 GROUP BY 列是索引的最左前綴

原理:這是實現松散索引掃描的關鍵。如果 GROUP BY col1, col2,那么創建一個 (col1, col2) 的復合索引是最優的。MySQL 可以直接在索引上進行分組,不需要額外的排序或哈希操作。

案例 1:統計每個車隊的運動員數量

  • SQLEXPLAIN SELECT team_id, COUNT(cyclist_id) FROM cyclists GROUP BY team_id;
  • 優化前 EXPLAIN 結果:可能顯示 Using temporary; Using filesort (如果 team_id 沒有索引)。
  • 優化后索引CREATE INDEX idx_team_id ON cyclists(team_id);
  • 優化后 EXPLAIN 結果:顯示 Using index?。
  • 原理:有了 idx_team_id 索引,MySQL 可以直接掃描這個索引。由于索引本身就是按 team_id 排序的,它可以高效地按 team_id 進行分組,無需創建臨時表或進行排序。
策略 2:WHERE 條件和 GROUP BY 條件同時利用索引

原理:如果 WHERE 條件能夠使用索引過濾,并且 GROUP BY 也能利用同一個索引的后續列,那么組合索引的效率會更高。將 GROUP BY 的列放在索引前部,WHERE 條件中用于過濾的列放在其后(特別是對于范圍查詢)。

案例 2:統計年齡大于 20 歲的男性運動員,按國籍和性別分組

  • SQLEXPLAIN SELECT nationality, gender, COUNT(cyclist_id) FROM cyclists WHERE age > 20 AND gender = '男' GROUP BY nationality, gender;
  • 優化前 EXPLAIN 結果:可能顯示 Using temporary; Using filesort
  • 優化思路:為了避免臨時表,GROUP BY 的列 (nationality, gender) 應該作為索引的最左前綴。WHERE 條件中的 agegender(等值條件)則放在其后。
  • 優化后索引CREATE INDEX idx_nationality_gender_age ON cyclists(nationality, gender, age);
  • 優化后 EXPLAIN 結果Using where; Using index (或 Using index condition),不再有 Using temporaryUsing filesort
  • 原理idx_nationality_gender_age 索引首先按 nationality 分組,然后按 gender 分組。WHERE age > 20gender = '男' 可以在掃描索引時進行過濾(索引條件下推)。這樣,整個分組過程都可以在索引上完成,無需臨時表。
策略 3:GROUP BYORDER BY 列的順序一致

原理:如果 GROUP BYORDER BY 使用相同的列且順序一致,MySQL 可以在一次索引掃描中同時完成兩者要求。

案例 3:統計每個車隊的運動員數量,并按車隊 ID 升序排列

  • SQLEXPLAIN SELECT team_id, COUNT(cyclist_id) FROM cyclists GROUP BY team_id ORDER BY team_id;
  • 優化前 EXPLAIN 結果:可能顯示 Using temporary; Using filesort
  • 優化后索引CREATE INDEX idx_team_id ON cyclists(team_id);
  • 優化后 EXPLAIN 結果Using index for group-by (或 Using index),沒有 Using temporaryUsing filesort
  • 原理:由于 team_id 索引本身就是有序的,MySQL 可以直接掃描索引,按 team_id 分組,同時這個順序也滿足了 ORDER BY team_id 的要求,從而同時避免了臨時表和文件排序。

其他 GROUP BY 優化建議

  • 避免在 GROUP BY 中使用函數或表達式:這會使得索引失效,強制 MySQL 使用臨時表進行分組。
  • 合理使用 HAVING 子句HAVINGGROUP BY 之后對分組結果進行過濾。如果能將過濾條件前置到 WHERE 子句中,會更早地減少需要分組的數據量,效率更高。
  • 調整 MySQL 配置參數:適當增大 tmp_table_size / max_heap_table_size,可以讓更多臨時表在內存中完成,減少磁盤 I/O。

5. LIMIT 分頁操作優化:大偏移量分頁的挑戰與突破

LIMIT 子句常用于分頁。當 offset 較小(前幾頁)時性能良好,但當 offset 變得非常大時,LIMIT 操作會變得異常緩慢,成為查詢的瓶頸。

5.1 LIMIT 分頁操作的底層原理

當執行 LIMIT offset, row_count 時,MySQL 的大致執行過程是:

  1. 掃描并排序:MySQL 首先會掃描(或通過索引)所有滿足 WHERE 條件的行,并進行 ORDER BY 排序。
  2. 跳過偏移量:然后,它會從排序后的結果集中,逐條跳過offset 條記錄。
  3. 返回指定數量:最后,它會收集從 offset 之后開始的 row_count 條記錄并返回。

問題出在哪里?

offset 很大時,即使 ORDER BY 列有索引,SELECT * 仍然是瓶頸。MySQL 為了確定最終要返回的 row_count 條記錄,它不得不掃描并處理前面 offset + row_count 條記錄。尤其當 SELECT * 時,MySQL 需要為每一條被跳過的記錄也執行回表操作,以獲取其完整的行數據。這意味著,隨著 offset 增大,MySQL 需要處理的數據量線性增加,導致大量的隨機 I/O 和 CPU 浪費。這就像你翻閱一本非常厚的書,要看第 10000 頁開始的 10 頁,你仍然需要從頭開始翻過前面的 9999 頁才能開始閱讀你真正想看的內容。

5.2 優化策略及原理

核心思想:減少 MySQL 掃描和跳過的數據量,特別是避免為被跳過的記錄執行昂貴的回表操作。

策略 1:使用覆蓋索引和子查詢優化大偏移量分頁(“延遲回表”)

原理:這是處理大偏移量分頁最常用且最有效的策略。它將“掃描大量數據并回表”轉化為“輕量級掃描索引定位主鍵 + 精準回表獲取少量完整數據”。

  • 第一步(子查詢):快速定位目標主鍵
    • 利用一個覆蓋索引(只包含排序字段和主鍵)來快速定位到 offset 后的第一條記錄的主鍵值。這一步只掃描索引,無需回表,效率極高。
  • 第二步(主查詢):精準回表獲取完整數據
    • 利用第一步中獲取到的少量主鍵值,使用 WHERE primary_key IN (...)WHERE primary_key > some_id LIMIT row_count 的方式,直接從主鍵索引中精確獲取所需的 row_count 條完整數據,避免了掃描和跳過大量不相關的數據。

案例:查詢 cyclists 表中按 age 降序排列的第 10001 到 10010 條記錄(即 LIMIT 10000, 10

  • 優化前 SQLSELECT * FROM cyclists ORDER BY age DESC LIMIT 10000, 10;

  • 優化前分析:假設 age 列有 idx_age (age DESC) 索引。MySQL 會掃描 idx_age 索引 10010 次。每次掃描到一個 (age, cyclist_id) 條目,為了滿足 SELECT *,它需要立即根據 cyclist_id 回表,獲取該行的所有其他列。這意味著,為了跳過前 10000 條記錄,MySQL 不得不執行 10000 次昂貴的隨機回表 I/O,即使這些數據最終都會被丟棄。

  • 優化后 SQL

    -- 確保 age 列上有索引,例如:CREATE INDEX idx_age ON cyclists(age DESC);
    -- 假設 cyclist_id 是主鍵SELECT c.*
    FROM cyclists c
    INNER JOIN (SELECT cyclist_idFROM cyclistsORDER BY age DESCLIMIT 10000, 10 -- 子查詢只獲取主鍵
    ) AS sub
    ON c.cyclist_id = sub.cyclist_id
    ORDER BY c.age DESC; -- 外部再次排序以確保最終結果正確順序
    
  • 原理

    1. 子查詢SELECT cyclist_id FROM cyclists ORDER BY age DESC LIMIT 10000, 10
      • MySQL 利用 idx_age 索引的有序性,從索引葉子節點開始掃描。
      • 由于子查詢只請求 cyclist_id(主鍵),而 cyclist_id 包含在二級索引的葉子節點中,因此這是一個覆蓋索引查詢無需回表
      • MySQL 只掃描索引 10010 次,然后直接從索引中獲取 10 個目標 cyclist_id。這一步 I/O 開銷極小,因為是順序掃描索引。
    2. 外層查詢SELECT c.* FROM cyclists c INNER JOIN ...
      • 外層查詢得到 10 個精確的 cyclist_id 列表。
      • MySQL 使用這 10 個 cyclist_id 去主鍵索引中進行查找,精確地獲取這 10 條完整的記錄
      • 主鍵查找是最快的查找方式,僅需要 10 次高效的隨機 I/O。
  • 優點:顯著減少了掃描的數據量和隨機回表 I/O,特別是對于大偏移量查詢,性能提升巨大。

  • 缺點:SQL 語句相對復雜。

策略 2:基于上次查詢結果進行優化(“書簽式”分頁或“游標式”分頁)

原理:不使用 OFFSET。而是記錄上次查詢結果的最后一條記錄的某個有序字段的值(通常是排序字段和主鍵),在下一次查詢時,利用這個值作為新的查詢起點。

  • 適用場景:實時滾動加載(如社交媒體的“加載更多”),或者用戶總是從上一頁或下一頁跳轉,而不是直接跳到任意頁碼。這種方法無法直接跳轉到任意頁

  • 示例:加載下一頁按 registration_time 倒序排列的用戶列表。

    首次加載(第一頁):

    SELECT cyclist_id, name, registration_time FROM cyclists ORDER BY registration_time DESC LIMIT 10;

    假設第一頁的最后一條記錄的 registration_time 是 '2025-01-10 10:00:00',其 cyclist_id 是 500。

    加載下一頁(第二頁)

    SELECT cyclist_id, name, registration_time
    FROM cyclists
    WHERE registration_time < '2025-01-10 10:00:00'OR (registration_time = '2025-01-10 10:00:00' AND cyclist_id < 500) -- 處理相同時間戳的情況
    ORDER BY registration_time DESC
    LIMIT 10;
    
  • 原理WHERE 子句能夠直接利用 registration_time 上的索引(或復合索引 (registration_time, cyclist_id))進行范圍查找。MySQL 只需要從上次查詢的結束點開始掃描,而不是從頭開始掃描并跳過大量記錄。LIMIT 10 限制了掃描的數據量。沒有 OFFSET,大大減少了處理的數據量。

  • 優點:性能極高,尤其適合“加載更多”的場景,I/O 效率幾乎與查詢第一頁相同。

  • 缺點:不能直接跳轉到任意頁碼;需要前端或應用記住上次查詢的“書簽”值。

策略 3:優化 COUNT(*) 查詢以獲取總頁數

原理:在分頁時,經常需要知道總記錄數來計算總頁數。SELECT COUNT(*) FROM table_name WHERE ... 在沒有索引的情況下,可能導致全表掃描。

  • 優化

    • 利用覆蓋索引:如果 COUNT(*) 可以在某個非空索引上完成,MySQL 可以只掃描索引,而不是掃描整個表。COUNT(*) 會選擇最小的索引進行掃描。
    • 估算代替精確計數:對于非常大的表,精確計數可能非常耗時。在某些業務場景下,可以接受一個大致的估算值(例如使用 EXPLAIN 結果中的 rows 字段),或者定期將 COUNT(*) 結果緩存到 Redis 等地方。
  • 示例SELECT COUNT(*) FROM cyclists WHERE gender = '男';

  • 優化:如果 gender 列有索引(CREATE INDEX idx_gender ON cyclists(gender);),MySQL 可以通過掃描該索引的葉子節點進行計數,實現覆蓋索引,比全表掃描快得多。


總結

SQL 優化是一個持續的、需要深入理解數據庫原理的過程。從 INSERT 的物理寫入順序,到 ORDER BYGROUP BY 如何利用索引避免昂貴的排序/臨時表,再到 LIMIT 分頁如何巧妙地規避大偏移量的性能陷阱,無不體現著對索引機制I/O 成本的深刻洞察。

核心優化策略始終圍繞著:

  • 選擇合適的主鍵:短小、順序、非空,降低所有二級索引的成本。
  • 創建合理的索引:特別是復合索引,使其能夠同時服務 WHEREORDER BYGROUP BY
  • 利用覆蓋索引:避免不必要的回表操作,將 I/O 降至最低。
  • 規避全表掃描和臨時表操作:理解 EXPLAIN 輸出,識別 Using filesortUsing temporary 等瓶頸,并針對性優化。
  • 掌握特殊場景優化:如大批量 INSERT 使用 LOAD DATA INFILE,大偏移量分頁的子查詢策略和游標式分頁。

通過持續的實踐、對 EXPLAIN 結果的分析以及對數據庫底層原理的理解,您將能夠編寫出更高效、更健壯的 SQL 查詢,為您的應用帶來卓越的性能。

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

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

相關文章

SQL 基礎入門

SQL 基礎入門 SQL&#xff08;全稱 Structured Query Language&#xff0c;結構化查詢語言&#xff09;是用于操作關系型數據庫的標準語言&#xff0c;主要用于數據的查詢、新增、修改和刪除。本文面向初學者&#xff0c;介紹 SQL 的基礎概念和核心操作。 1. 常見的 SQL 數據…

HTTP 請求協議簡單介紹

目錄 常見的 HTTP 響應頭字段 Java 示例代碼&#xff1a;發送 HTTP 請求并處理響應 代碼解釋&#xff1a; 運行結果&#xff1a; 文件名&#xff1a; 總結&#xff1a; HTTP&#xff08;HyperText Transfer Protocol&#xff09;是用于客戶端與服務器之間通信的協議。它定…

《100天精通Python——基礎篇 2025 第5天:鞏固核心知識,選擇題實戰演練基礎語法》

目錄 一、踏上Python之旅二、Python輸入與輸出三、變量與基本數據類型四、運算符五、流程控制 一、踏上Python之旅 1.想要輸出 I Love Python,應該使用()函數。 A.printf() B.print() C.println() D.Print() 在Python中想要在屏幕中輸出內容&#xff0c;應該使用print()函數…

求解一次最佳平方逼近多項式

例 設 f ( x ) 1 x 2 f(x)\sqrt{1x^2} f(x)1x2 ?&#xff0c;求 [ 0 , 1 ] [0,1] [0,1]上的一個一次最佳平方逼近多項式。 解 &#xff1a; d 0 ∫ 0 1 1 x 2 d x 1 2 ln ? ( 1 2 ) 2 2 ≈ 1.147 d_0\int_{0}^{1}\sqrt{1x^2}dx\frac{1}{2}\ln(1\sqrt{2})\frac{\sqrt…

在Ubuntu上使用 dd 工具制作U盤啟動盤

在Ubuntu上使用 dd 工具制作U盤啟動盤 在Linux系統中&#xff0c;dd 是一個功能強大且原生支持的命令行工具&#xff0c;常用于復制文件和轉換數據。它也可以用來將ISO鏡像寫入U盤&#xff0c;從而創建一個可啟動的操作系統安裝盤。雖然圖形化工具&#xff08;如 Startup Disk…

如何理解OSI七層模型和TCP/IP四層模型?HTTP作為如何保存用戶狀態?多服務器節點下 Session方案怎么做

本篇概覽&#xff1a; OSI 七層模型是什么&#xff1f;每一層的作用是什么&#xff1f;TCP/IP四層模型和OSI七層模型的區別是什么&#xff1f; HTTP 本身是無狀態協議&#xff0c;HTTP如何保存用戶狀態? 能不能具體說一下Cookie的工作原理、生命周期、作用域&#xff1f;使用…

深入剖析 RocketMQ 中的 DefaultMQPushConsumerImpl:消息推送消費的核心實現

前言 在 Apache RocketMQ 的消息消費體系中&#xff0c;RocketMQ 提供了DefaultMQPushConsumer&#xff08;推送消費&#xff09;和DefaultMQPullConsumer&#xff08;拉取消費&#xff09;兩種主要消費方式。DefaultMQPushConsumer與DefaultMQPullConsumer在消息獲取方式&…

Linux編程:2、進程基礎知識

一、進程基本概念 1、進程與程序的區別 程序&#xff1a;靜態的可執行文件&#xff08;如電腦中的vs2022安裝程序&#xff09;。進程&#xff1a;程序的動態執行過程&#xff08;如啟動后的vs2022實例&#xff09;&#xff0c;是操作系統分配資源的單位&#xff08;如 CPU 時…

React Router 中 navigate 后瀏覽器返回按鈕不起作用的問題記錄

React Router 中 navigate 后瀏覽器返回按鈕不起作用的問題記錄 在使用 React Router&#xff08;v6&#xff09;開發項目時&#xff0c;我遇到了一個讓人困惑的問題&#xff1a; 當我從 /article 頁面使用 navigate("/article/next") 進行跳轉后&#xff0c;點擊瀏…

[面試精選] 0094. 二叉樹的中序遍歷

文章目錄 1. 題目鏈接2. 題目描述3. 題目示例4. 解題思路5. 題解代碼6. 復雜度分析 1. 題目鏈接 94. 二叉樹的中序遍歷 - 力扣&#xff08;LeetCode&#xff09; 2. 題目描述 給定一個二叉樹的根節點 root &#xff0c;返回 它的 中序 遍歷 。 3. 題目示例 示例 1 : 輸入&…

Addressable-配置相關

1、Profile 概述窗口配置 主要用于配置Addressable打包&#xff08;構建&#xff09;加載AB包時使用的一些變量,這些變量定義了 在哪里保存打包&#xff08;構建&#xff09;的AB包運行時在哪里加載AB包 可以添加自定義變量&#xff0c;以便在打包加載時使用,之后在設置 組中…

aws(學習筆記第四十三課) s3_sns_sqs_lambda_chain

文章目錄 aws(學習筆記第四十三課) s3_sns_sqs_lambda_chain學習內容&#xff1a;1. 整體架構1.1 代碼鏈接1.2 整體架構1.3 測試代碼需要的修改1.3.1 unit test代碼中引入stack的修改1.3.2 test_outputs_created代碼中把錯誤的去掉 2. 代碼解析2.1 生成dead_letter_queue死信隊…

Python訓練營打卡Day43

kaggle找到一個圖像數據集&#xff0c;用cnn網絡進行訓練并且用grad-cam做可視化 進階&#xff1a;并拆分成多個文件 config.py import os# 基礎配置類 class Config:def __init__(self):# Kaggle配置self.kaggle_username "" # Kaggle用戶名self.kaggle_key &quo…

hive 3集成Iceberg 1.7中的Java版本問題

hive 3.1.3 集成iceberg 1.7.2創建Iceberg表報錯如下&#xff1a; Exception in thread "main" java.lang.UnsupportedClassVersionError: org/apache/iceberg/mr/hive/HiveIcebergStorageHandler has been compiled by a more recent version of the Java Runtime …

文本切塊技術(Splitter)

為什么要分塊&#xff1f; 將長文本分解成適當大小的片段&#xff0c;以便于嵌入、索引和存儲&#xff0c;并提高檢索的精確度。 用ChunkViz工具可視化分塊 在線使用 ChunkViz github https://github.com/gkamradt/ChunkViz 如何確定大模型所能接受的最長上下文 可以從…

C++:用 libcurl 發送一封帶有附件的郵件

編寫mingw C 程序&#xff0c;用 libcurl 發送一封帶有附件的郵件 下面是一個使用 MinGW 編譯的 C 程序&#xff0c;使用 libcurl 發送帶附件的郵件。這個程序完全通過代碼實現 SMTP 郵件發送&#xff0c;不依賴外部郵件客戶端&#xff1a; // send_email.cpp #include <i…

tensorflow image_dataset_from_directory 訓練數據集構建

以數據集 https://www.kaggle.com/datasets/vipoooool/new-plant-diseases-dataset 為例 目錄結構 訓練圖像數據集要求&#xff1a; 主目錄下包含多個子目錄&#xff0c;每個子目錄代表一個類別。每個子目錄中存儲屬于該類別的圖像文件。 例如 main_directory/ ...cat/ ...…

遨游Spring AI:第一盤菜Hello World

Spring AI的正式版已經發布了&#xff0c;很顯然&#xff0c;接下來我們要做的事情就是寫一個Hello World。 總體思路就是在本地搭建一個簡單的大模型&#xff0c;然后編寫Spring AI代碼與模型進行交互。 分五步&#xff1a; 1. 安裝Ollama&#xff1b; 2. 安裝DeepSeek&…

華為云Flexus+DeepSeek征文|基于華為云Flexus X和DeepSeek-R1打造個人知識庫問答系統

目錄 前言 1 快速部署&#xff1a;一鍵搭建Dify平臺 1.1 部署流程詳解 1.2 初始配置與登錄 2 構建專屬知識庫 2.1 進入知識庫模塊并創建新庫 2.2 選擇數據源導入內容 2.3 上傳并識別多種文檔格式 2.4 文本處理與索引構建 2.5 保存并完成知識庫創建 3接入ModelArts S…

Java優化:雙重for循環

在工作中&#xff0c;經常性的會出現在兩張表中查找相同ID的數據&#xff0c;許多開發者會使用兩層for循環嵌套&#xff0c;雖然實現功能沒有問題&#xff0c;但是效率極低&#xff0c;一下是一個簡單的優化過程&#xff0c;代碼耗時湊從26856ms優化到了748ms。 功能場景 有兩…