在遊戲開發中,角色隨機命名與離線玩家發獎是常見需求。本文分享兩個實戰案例:利用 RAND() 高效抽取未使用名稱,以及透過游標批量處理百萬級玩家資料。
需求: 從預先建立的 names 表中隨機挑選一個符合性別且未被使用的名稱。
表結構:
CREATE TABLE names (
id INT PRIMARY KEY AUTO_INCREMENT,
rname VARCHAR(50),
sex TINYINT -- 1 男 2 女
);
存儲過程實現:
CREATE PROCEDURE GetRandomName(
IN _sex TINYINT,
IN _world INT,
OUT _name VARCHAR(50),
OUT returnvalue INT
)
BEGIN
DECLARE randnum INT DEFAULT 0;
-- 取得隨機起始點(假設表約 30 萬筆)
SELECT FLOOR(RAND() * 309034) INTO randnum;
-- 從隨機點向後查找第一個符合條件的名稱
SELECT rname INTO _name
FROM names
WHERE sex = _sex
AND rname NOT IN (SELECT Name FROM longwen.player WHERE WorldID = _world)
AND id > randnum
LIMIT 1;
SET returnvalue = 0;
END
優化說明:
id > randnum 避免 ORDER BY RAND() 全表掃描,效率提升數十倍。id <= randnum 的查詢,確保必定取得結果。需求: 維護期間需給所有玩家發放補償,包含離線玩家,直接操作資料庫。
初版錯誤寫法(只能處理一筆):
DECLARE _cursor CURSOR FOR SELECT RoleID FROM player;
REPEAT
FETCH _cursor INTO _roleId;
-- 錯誤:在迴圈內再執行 SELECT 會導致游標狀態混亂
SELECT ...;
REPLACE INTO email(roleID, datas) VALUES(_roleId, 'test');
UNTIL _done END REPEAT;
正確寫法(將查詢移至游標宣告):
CREATE PROCEDURE SendOfflineRewards()
BEGIN
DECLARE _roleId BIGINT;
DECLARE _done INT DEFAULT 0;
-- 游標直接查詢需發獎的玩家(排除已有郵件者)
DECLARE _cursor CURSOR FOR
SELECT RoleID FROM player
WHERE RoleID NOT IN (SELECT roleID FROM email WHERE LENGTH(datas) <> 0);
-- 捕獲「無資料」狀態 (SQLSTATE '02000')
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET _done = 1;
OPEN _cursor;
SET _done = 0;
REPEAT
FETCH _cursor INTO _roleId;
IF NOT _done THEN
REPLACE INTO email(roleID, datas) VALUES(_roleId, 'test');
END IF;
UNTIL _done END REPEAT;
CLOSE _cursor;
END
SELECT INTO 或子查詢結果為空表UPDATE/DELETE 影響行數為零FETCH 游標超過結果集最後一行理解這三點,才能正確設計游標結束條件,避免漏處理或無限迴圈。
id > RAND()*MAX_ID + LIMIT 1 遠優於 ORDER BY RAND()。DECLARE CURSOR 完成,迴圈內只做 FETCH 與業務邏輯。COMMIT),避免長事務鎖表。