從 SQL Server 遷移存儲過程時,@@ROWCOUNT 的替代方案是 MySQL 開發者必須掌握的核心技能。FOUND_ROWS() 與 ROW_COUNT() 雖名稱相似,但適用場景、語義邏輯、返回值規則截然不同。本文基於官方文檔與實戰驗證,系統拆解兩函數的三種/四種工作模式、分頁優化模式、存儲過程替代模式、JDBC 兼容性陷阱及復制影響。
官方文檔: Information Functions - FOUND_ROWS()
SQL_CALC_FOUND_ROWS + LIMIT → 獲取無 LIMIT 總行數(分頁核心)SELECT SQL_CALC_FOUND_ROWS * FROM users WHERE status = 1 LIMIT 10;
SELECT FOUND_ROWS(); -- 返回滿足 WHERE 條件的總行數,忽略 LIMIT
COUNT(*) + LIMIT) 快,因避免了二次掃描與結果集傳輸。COUNT(*) 細微差異: 若查詢列允許 NULL,FOUND_ROWS() 等價於 COUNT(column)(不計 NULL 行),而 COUNT(*) 計所有行。SQL_CALC_FOUND_ROWS、無 LIMIT → 返回上一條 SELECT 實際返回行數SELECT * FROM users WHERE id < 100; -- 返回 50 行
SELECT FOUND_ROWS(); -- 返回 50
SQL_CALC_FOUND_ROWS、有 LIMIT → 返回 LIMIT 掃描到的最大行數(非結果集行數)SELECT * FROM users LIMIT 10; -- 掃描 10 行,返回 10 行
SELECT FOUND_ROWS(); -- 返回 10
SELECT * FROM users LIMIT 50, 10; -- 從第 50 行掃描 10 行,掃描到第 60 行
SELECT FOUND_ROWS(); -- 返回 60 (掃描上限)
-- 關鍵驗證:WHERE 條件無匹配行時
SELECT * FROM users WHERE id = 999999 LIMIT 100, 5; -- 結果集為空
SELECT FOUND_ROWS(); -- 返回 0!因 LIMIT 根本未執行掃描
-- 結論:FOUND_ROWS() 反映的是「執行器掃描上限」,而非「結果集大小」
-- 假設 users 表有 1000 行,pwd 列全為 'xx'
SELECT * FROM users WHERE pwd = 'xx' LIMIT 100, 5;
-- 執行器:全表掃描過濾 pwd='xx' → 得到 1000 行中間結果 → 跳過前 100 行 → 取後 5 行
SELECT FOUND_ROWS(); -- 返回 105 (100+5),即中間結果集掃描上限
-- 證明:FOUND_ROWS() 作用於「中間結果集應用 LIMIT 後的掃描位置」,與原始表行數無關
SQL_CALC_FOUND_ROWS + LIMIT + FOUND_ROWS() 獲取總頁數,單次掃描完成「數據+總數」。SQL_CALC_FOUND_ROWS 時,緊跟 SELECT 執行 IF FOUND_ROWS() < 1 THEN ...,完美替代 SQL Server IF @@ROWCOUNT < 1。官方文檔: Information Functions - ROW_COUNT()
| 語句類型 | 返回值 | 說明 |
|---|---|---|
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 旗標ROW_COUNT() = 實際改變值的行數。UPDATE t SET c=1 WHERE c=1 → 返回 0 (值未變)。ROW_COUNT() = WHERE 條件匹配的行數。同上語句 → 返回匹配行數。SQL%ROWCOUNT、SQL Server @@ROWCOUNT 均為「匹配行數」。啟用此旗標可使 MySQL 行為與兩者一致。Statement.getUpdateCount() 返回「匹配行數」(等價 CLIENT_FOUND_ROWS=1 行為)。ROW_COUNT() 默認返回「實際修改行數」(等價 CLIENT_FOUND_ROWS=0 行為)。ROW_COUNT() 不一致!ROW_COUNT() 獲取「實際修改行數」(業務邏輯常需此語義)。getUpdateCount()。useAffectedRows=true 可使 JDBC 返回「實際修改行數」,但違背 JDBC 規範,會破壞多數依賴「匹配行數」的框架(如 MyBatis、Hibernate),極不推薦。DELIMITER //
CREATE PROCEDURE sp_login_or_register (
OUT p_user_id BIGINT,
IN p_pwd VARCHAR(32),
IN p_mobile_code VARCHAR(16),
IN p_reg_ip VARCHAR(16)
)
BEGIN
-- 檢查用戶是否存在
IF EXISTS (SELECT 1 FROM users WHERE mobile_code = p_mobile_code) THEN
-- 嘗試驗證密碼並獲取 ID
SELECT user_id INTO p_user_id
FROM users
WHERE mobile_code = p_mobile_code AND pwd = p_pwd;
-- 關鍵:FOUND_ROWS() 判斷上一條 SELECT 是否返回行
IF FOUND_ROWS() < 1 THEN
SET p_user_id = -1; -- 密碼錯誤
END IF;
ELSE
-- 註冊新用戶
INSERT INTO users (pwd, mobile_code, visibility, level, reg_time, reg_ip, collector, collected)
VALUES (p_pwd, p_mobile_code, 6, 6, NOW(), p_reg_ip, 0, 0);
SET p_user_id = LAST_INSERT_ID();
END IF;
END //
DELIMITER ;
核心邏輯: SELECT ... INTO 若無匹配行,p_user_id 變為 NULL,但不報錯。緊跟的 FOUND_ROWS() 精準捕獲「是否查到數據」,實現「查不到 = 密碼錯誤」的業務判斷。
-- 官方文檔明確警告:
-- 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,或避免在觸發器/存儲過程中依賴這兩函數進行關鍵邏輯分支(改用應用層判斷)。useUnicode、characterEncoding# 經典 JDBC URL
jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=UTF-8
# useUnicode=true (默認) 時,characterEncoding 指定具體編碼
# 可簡化為:
jdbc:mysql://localhost:3306/db?characterEncoding=UTF-8
# 驅動自動啟用 Unicode 處理
FOUND_ROWS() 是「SELECT 掃描上限感知器」,ROW_COUNT() 是「DML 影響行數報告器」。掌握 SQL_CALC_FOUND_ROWS 分頁模式、FOUND_ROWS() 存儲過程空判斷模式、ROW_COUNT() 與 CLIENT_FOUND_ROWS/JDBC 的語義差異、RBR 復制下的可靠性,即可在遷移、分頁、批量操作、跨語言集成等全場景游刃有余。切記:JDBC getUpdateCount() ≠ ROW_COUNT(),存儲過程回傳 OUT 參數是獲取「實際修改行數」的正規路徑。