MySQL存储过程:循环遍历查询结果集详细解析

原创 2025-06-14 10:16:57编程技术
378

在MySQL数据库开发中,存储过程通过预编译的SQL语句集合实现复杂业务逻辑的封装。当需要处理动态查询结果集时,循环遍历技术成为核心工具。本文ZHANID工具网将系统解析MySQL存储过程中循环遍历查询结果集的实现方法、技术细节和最佳实践。

一、核心实现机制

1.1 游标(Cursor)基础

游标是MySQL存储过程中用于逐行处理结果集的关键机制,其工作原理如下:

DELIMITER //
CREATE PROCEDURE process_cursor()
BEGIN
    -- 声明变量
    DECLARE done INT DEFAULT FALSE;
    DECLARE user_id INT;
    DECLARE user_name VARCHAR(100);
    
    -- 声明游标
    DECLARE user_cursor CURSOR FOR 
        SELECT id, name FROM users WHERE status = 'active';
    
    -- 声明异常处理
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    -- 打开游标
    OPEN user_cursor;
    
    -- 循环处理
    read_loop: LOOP
        FETCH user_cursor INTO user_id, user_name;
        IF done THEN
            LEAVE read_loop;
        END IF;
        
        -- 业务逻辑处理
        UPDATE user_stats SET last_active = NOW() WHERE user_id = user_id;
        INSERT INTO activity_log (user_id, action, timestamp) 
        VALUES (user_id, 'login', NOW());
    END LOOP;
    
    -- 关闭游标
    CLOSE user_cursor;
END //
DELIMITER ;

1.2 异常处理机制

MySQL通过DECLARE HANDLER实现游标遍历的边界控制:

  • CONTINUE HANDLER:遇到异常时继续执行

  • EXIT HANDLER:遇到异常时退出循环

  • NOT FOUND:当游标遍历完成时触发

二、典型应用场景

2.1 数据同步与清洗

CREATE PROCEDURE sync_user_data()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE ext_id INT;
    DECLARE ext_email VARCHAR(100);
    DECLARE local_id INT;
    
    DECLARE external_cursor CURSOR FOR 
        SELECT id, email FROM external_users WHERE last_updated > DATE_SUB(NOW(), INTERVAL 1 DAY);
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    OPEN external_cursor;
    
    read_loop: LOOP
        FETCH external_cursor INTO ext_id, ext_email;
        IF done THEN LEAVE read_loop; END IF;
        
        -- 检查本地是否存在
        SELECT id INTO local_id FROM users WHERE email = ext_email LIMIT 1;
        
        -- 更新或插入
        IF local_id IS NOT NULL THEN
            UPDATE users SET last_sync = NOW() WHERE id = local_id;
        ELSE
            INSERT INTO users (email, created_at) VALUES (ext_email, NOW());
        END IF;
    END LOOP;
    
    CLOSE external_cursor;
END;

2.2 复杂业务逻辑处理

CREATE PROCEDURE calculate_monthly_stats()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE product_id INT;
    DECLARE total_sales DECIMAL(12,2);
    DECLARE month_sales DECIMAL(12,2);
    
    DECLARE product_cursor CURSOR FOR 
        SELECT id FROM products WHERE is_active = 1;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    OPEN product_cursor;
    
    read_loop: LOOP
        FETCH product_cursor INTO product_id;
        IF done THEN LEAVE read_loop; END IF;
        
        -- 计算当月销售额
        SELECT SUM(amount) INTO month_sales 
        FROM orders 
        WHERE product_id = product_id 
        AND order_date BETWEEN DATE_FORMAT(NOW(), '%Y-%m-01') AND LAST_DAY(NOW());
        
        -- 更新统计表
        UPDATE product_stats 
        SET monthly_sales = month_sales, 
            last_updated = NOW() 
        WHERE product_id = product_id;
        
        -- 触发库存预警
        IF month_sales > 10000 THEN
            INSERT INTO alerts (product_id, message, created_at)
            VALUES (product_id, 'High sales volume detected', NOW());
        END IF;
    END LOOP;
    
    CLOSE product_cursor;
END;

三、性能优化策略

3.1 批量处理优化

CREATE PROCEDURE batch_update_status()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE user_id INT;
    DECLARE batch_size INT DEFAULT 100;
    DECLARE i INT DEFAULT 0;
    
    DECLARE temp_ids TEXT DEFAULT '';
    
    DECLARE user_cursor CURSOR FOR 
        SELECT id FROM users WHERE last_login < DATE_SUB(NOW(), INTERVAL 90 DAY);
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    OPEN user_cursor;
    
    read_loop: LOOP
        FETCH user_cursor INTO user_id;
        IF done THEN LEAVE read_loop; END IF;
        
        -- 构建批量ID列表
        SET temp_ids = CONCAT(temp_ids, ',', user_id);
        SET i = i + 1;
        
        -- 达到批量大小时执行更新
        IF i >= batch_size THEN
            SET temp_ids = SUBSTRING(temp_ids, 2); -- 去除首逗号
            SET @sql = CONCAT('UPDATE users SET status = ''inactive'' WHERE id IN (', temp_ids, ')');
            PREPARE stmt FROM @sql;
            EXECUTE stmt;
            DEALLOCATE PREPARE stmt;
            
            SET temp_ids = '';
            SET i = 0;
        END IF;
    END LOOP;
    
    -- 处理剩余记录
    IF i > 0 THEN
        SET temp_ids = SUBSTRING(temp_ids, 2);
        SET @sql = CONCAT('UPDATE users SET status = ''inactive'' WHERE id IN (', temp_ids, ')');
        PREPARE stmt FROM @sql;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
    END IF;
    
    CLOSE user_cursor;
END;

3.2 索引优化建议

  1. 查询字段索引:确保游标查询使用的字段有适当索引

  2. 批量操作索引:批量更新/删除时,确保WHERE条件字段已索引

  3. 避免全表扫描:在游标查询中添加必要的WHERE条件

mysql.webp

四、高级技术实现

4.1 嵌套游标处理

CREATE PROCEDURE process_orders_with_items()
BEGIN
    DECLARE done_orders INT DEFAULT FALSE;
    DECLARE done_items INT DEFAULT FALSE;
    DECLARE order_id INT;
    DECLARE order_total DECIMAL(12,2);
    DECLARE item_id INT;
    DECLARE item_price DECIMAL(12,2);
    DECLARE item_qty INT;
    
    DECLARE order_cursor CURSOR FOR 
        SELECT id, total FROM orders WHERE status = 'processing';
    
    DECLARE item_cursor CURSOR FOR 
        SELECT id, price, quantity FROM order_items WHERE order_id = order_id;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND 
        BEGIN
            IF done_orders THEN
                SET done_items = TRUE;
            ELSE
                SET done_orders = TRUE;
            END IF;
        END;
    
    OPEN order_cursor;
    
    order_loop: LOOP
        FETCH order_cursor INTO order_id, order_total;
        IF done_orders THEN LEAVE order_loop; END IF;
        
        -- 重置标志
        SET done_items = FALSE;
        OPEN item_cursor;
        
        item_loop: LOOP
            FETCH item_cursor INTO item_id, item_price, item_qty;
            IF done_items THEN LEAVE item_loop; END IF;
            
            -- 处理订单项
            UPDATE inventory SET stock = stock - item_qty 
            WHERE product_id = (SELECT product_id FROM order_items WHERE id = item_id);
            
            INSERT INTO order_item_log (order_id, item_id, action, timestamp)
            VALUES (order_id, item_id, 'processed', NOW());
        END LOOP;
        
        CLOSE item_cursor;
        
        -- 更新订单状态
        UPDATE orders SET status = 'completed', processed_at = NOW() 
        WHERE id = order_id;
    END LOOP;
    
    CLOSE order_cursor;
END;

4.2 动态SQL游标

CREATE PROCEDURE dynamic_cursor_example(IN table_name VARCHAR(64), IN status_value VARCHAR(20))
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE id_val INT;
    DECLARE name_val VARCHAR(100);
    DECLARE sql_query TEXT;
    
    -- 构建动态SQL
    SET @sql = CONCAT('DECLARE dynamic_cursor CURSOR FOR SELECT id, name FROM ', table_name, 
                     ' WHERE status = ''', status_value, '''');
    PREPARE stmt FROM @sql;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    OPEN dynamic_cursor;
    
    read_loop: LOOP
        FETCH dynamic_cursor INTO id_val, name_val;
        IF done THEN LEAVE read_loop; END IF;
        
        -- 处理数据
        INSERT INTO audit_log (table_name, record_id, action, timestamp)
        VALUES (table_name, id_val, CONCAT('status changed to ', status_value), NOW());
    END LOOP;
    
    CLOSE dynamic_cursor;
END;

五、最佳实践与注意事项

5.1 资源管理规范

  1. 及时关闭游标:确保在异常处理和正常流程中都关闭游标

  2. 释放预处理语句:使用DEALLOCATE PREPARE释放动态SQL资源

  3. 限制事务范围:将游标操作限制在最小必要的事务范围内

5.2 错误处理机制

CREATE PROCEDURE safe_cursor_example()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        -- 记录错误
        INSERT INTO error_log (message, stack_trace, created_at)
        VALUES (CONCAT('Error in safe_cursor_example: ', SQLSTATE), 
                (SELECT GROUP_CONCAT(CONCAT(line_number, ': ', statement) 
                SEPARATOR '\n' FROM information_schema.ROUTINES 
                WHERE ROUTINE_NAME = 'safe_cursor_example'), 
                NOW());
        
        -- 回滚事务
        IF @@autocommit = 0 THEN
            ROLLBACK;
        END IF;
        
        -- 重新抛出异常
        RESIGNAL;
    END;
    
    -- 正常业务逻辑
    START TRANSACTION;
    -- ...游标操作...
    COMMIT;
END;

5.3 性能监控指标

  1. 执行时间:记录存储过程执行时间

  2. 游标效率:监控FETCH操作的平均耗时

  3. 内存使用:关注临时表和游标占用的内存

六、总结

MySQL存储过程中的循环遍历技术为复杂数据处理提供了强大支持,其核心要点包括:

  • 游标机制:实现结果集的逐行处理

  • 异常控制:确保流程的健壮性

  • 性能优化:通过批量处理和索引优化提升效率

  • 资源管理:规范游标和预处理语句的生命周期

在实际应用中,应根据业务复杂度选择合适的实现方式:

  • 简单场景:单层游标+基本处理

  • 复杂场景:嵌套游标+动态SQL

  • 高性能需求:批量处理+事务控制

通过合理运用这些技术,可以构建出高效、可靠的数据库处理逻辑,满足各种业务需求。

mysql 存储过程 循环遍历
THE END
战地网
频繁记录吧,生活的本意是开心

相关推荐