從 SQL Server 遷移存儲過程時,@@ROWCOUNT 的 MySQL 替代方案選型直接影響邏輯正確性。FOUND_ROWS() 與 ROW_COUNT() 雖同為「行數感知函數」,但觸發條件、返回語義、作用域截然不同。本文基於 MySQL 5.6 官方文檔與大量實戰驗證,系統構建兩函數的完整行為模型、適用場景決策樹、遷移替換模板及 JDBC 集成陷阱規避指南。
| 函數 | 核心角色 | 感知對象 | 典型返回值 |
|---|---|---|---|
FOUND_ROWS() | SELECT 掃描上限感知器 | 上一條 SELECT 執行過程 | 總行數 / 掃描上限 / 結果集行數 |
ROW_COUNT() | DML 影響行數報告器 | 上一條 INSERT/UPDATE/DELETE 等 DML | 受影響行數 / -1 (SELECT有結果集) / 0 (DDL) |
SQL_CALC_FOUND_ROWS + LIMIT → 獲取無 LIMIT 總行數(分頁黃金搭檔)SELECT SQL_CALC_FOUND_ROWS * FROM articles WHERE status='published' LIMIT 20;
SELECT FOUND_ROWS(); -- 返回符合條件的總文章數,如 1523
SELECT COUNT(*) ... + SELECT ... LIMIT 雙查詢快:避免二次全表掃描、避免傳輸完整結果集。COUNT(*) 差異: 若選擇列含 NULL,FOUND_ROWS() 等價 COUNT(col)(不計 NULL),COUNT(*) 計所有行。SET @total = FOUND_ROWS(); 變量保存,因函數值僅在緊跟的語句有效,後續語句會重置。SQL_CALC_FOUND_ROWS、無 LIMIT → 返回上一條 SELECT 實際返回行數SELECT * FROM users WHERE created_at > '2024-01-01'; -- 返回 42 行
SELECT FOUND_ROWS(); -- 返回 42
SQL_CALC_FOUND_ROWS、有 LIMIT → 返回 LIMIT 掃描到的最大行數(非結果集行數)SELECT * FROM logs LIMIT 10; -- 掃描 10 行,返回 10 行
SELECT FOUND_ROWS(); -- 返回 10
SELECT * FROM logs LIMIT 1000, 50; -- 從第 1000 行掃描 50 行,掃描到第 1050 行
SELECT FOUND_ROWS(); -- 返回 1050
-- 關鍵邊界:WHERE 無匹配時
SELECT * FROM users WHERE id = 999999 LIMIT 100, 5; -- 結果集空
SELECT FOUND_ROWS(); -- 返回 0!因 LIMIT 根本未執行掃描
-- 結論:反映「執行器掃描位置上限」,而非「結果集大小」
-- 假設表 t 有 1000 行,col='x' 全滿足
SELECT * FROM t WHERE col='x' LIMIT 100, 5;
-- 執行器:全表掃描 → 過濾 col='x' 得 1000 行中間結果 → 跳過 100 行 → 取 5 行
SELECT FOUND_ROWS(); -- 返回 105 (100+5)
-- 證明:FOUND_ROWS() 作用於「中間結果集應用 LIMIT 後的掃描終點」
| 語句類型 | 返回值 | 關鍵細節 |
|---|---|---|
DDL (CREATE/DROP TABLE) | 0 | 不計行數 |
DML (INSERT/UPDATE/DELETE) | 受影響行數 | UPDATE 默認返回實際修改行數 (值改變)。連接啟用 CLIENT_FOUND_ROWS 旗標時返回匹配行數 (含值未變) |
ALTER TABLE, LOAD DATA INFILE | 受影響行數 | 5.6+ 新增支援 |
SELECT (有結果集) | -1 | 如 SELECT * FROM t |
SELECT ... INTO OUTFILE | 寫入文件行數 | 無結果集時返回行數 |
SIGNAL | 0 |
CLIENT_FOUND_ROWS=0): ROW_COUNT() = 實際改變值的行數。UPDATE t SET c=1 WHERE c=1 → 返回 0。CLIENT_FOUND_ROWS=1): ROW_COUNT() = WHERE 匹配行數。同上 → 返回匹配行數。SQL%ROWCOUNT、SQL Server @@ROWCOUNT 均為「匹配行數」。啟用此旗標使 MySQL 行為與兩者一致。Statement.getUpdateCount() 返回「匹配行數」(等價 CLIENT_FOUND_ROWS=1)。ROW_COUNT() 默認返回「實際修改行數」(等價 CLIENT_FOUND_ROWS=0)。executeUpdate() 獲取的計數與存儲過程中 ROW_COUNT() 不一致。ROW_COUNT() 並通過 OUT 參數返回。useAffectedRows=true: 可使 JDBC 返回「實際修改行數」,但違背 JDBC 規範,會破壞 MyBatis、Hibernate 等依賴「匹配行數」語義的框架,極不推薦生產使用。DELIMITER //
CREATE PROCEDURE sp_user_login (
OUT p_user_id BIGINT,
IN p_mobile VARCHAR(16),
IN p_pwd VARCHAR(32)
)
BEGIN
DECLARE v_count INT DEFAULT 0;
-- 嘗試查詢匹配用戶
SELECT user_id INTO p_user_id
FROM users
WHERE mobile = p_mobile AND pwd = p_pwd;
-- 關鍵:FOUND_ROWS() 精準捕獲「是否查到數據」
IF FOUND_ROWS() < 1 THEN
SET p_user_id = -1; -- 無匹配 = 密碼錯誤或用戶不存在
END IF;
-- 對比:ROW_COUNT() 此處返回 -1 (SELECT有結果集),無法用於此判斷
END //
DELIMITER ;
遷移決策樹:
FOUND_ROWS() (緊跟 SELECT)。ROW_COUNT() (緊跟 INSERT/UPDATE/DELETE)。CLIENT_FOUND_ROWS 或存儲過程用 ROW_COUNT() 配合邏輯轉換。-- 官方文檔:
-- FOUND_ROWS() is not replicated reliably using statement-based replication.
-- ROW_COUNT() is not replicated reliably using statement-based replication.
-- This function is automatically replicated using row-based replication.
binlog_format=ROW,或避免在觸發器/存儲過程關鍵分支依賴這兩函數(移至應用層判斷)。# 經典 URL
jdbc:mysql://host:3306/db?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
# useUnicode=true (默認) 時,characterEncoding 指定編碼
# 可簡化為:
jdbc:mysql://host:3306/db?characterEncoding=UTF-8
# 驅動自動啟用 Unicode 處理
# zeroDateTimeBehavior=convertToNull:讀取 '0000-00-00' 日期返回 null,避免 JDBC 異常
FOUND_ROWS() 專司「SELECT 掃描上限」,ROW_COUNT() 專司「DML 影響行數」。掌握 SQL_CALC_FOUND_ROWS 分頁模式、FOUND_ROWS() 空結果集判斷模式、ROW_COUNT() 與 CLIENT_FOUND_ROWS/JDBC 的語義邊界、RBR 下的復制可靠性,即可在存儲過程遷移、高效分頁、批量寫入、跨語言集成等全場景精準決策。鐵律:JDBC getUpdateCount() ≠ ROW_COUNT(),存儲過程 OUT 參數是獲取「實際修改行數」的標準通道。