1. 参数默认值的坑从何而来

当我们在MySQL中编写存储过程时,很多开发者会自然联想到像其他数据库(如SQL Server)那样给参数设置默认值。但实际执行时会发现这样的写法埋着雷:

-- 错误示例:MySQL不支持DEFAULT关键字
DELIMITER $$
CREATE PROCEDURE GetUserInfo(
    IN userId INT DEFAULT 0  -- 这里会报语法错误
)
BEGIN
    SELECT * FROM users WHERE id = userId;
END$$
DELIMITER ;

执行上述代码时,MySQL会直接抛出错误(Error 1064),因为MySQL全版本都不支持存储过程参数的DEFAULT语法。这个特性缺失导致很多从其他数据库迁移过来的系统翻车,特别是当开发者在应用程序中省略参数调用时,会出现参数数量不匹配的致命错误。

2. 存储过程参数的正确设置姿势

既然MySQL不支持参数默认值,我们可以用逻辑判断模拟这个功能:

DELIMITER $$
CREATE PROCEDURE GetOrderList(
    IN startDate DATE,
    IN endDate DATE,
    IN pageSize INT
)
BEGIN
    -- 设置pageSize默认值
    IF pageSize IS NULL THEN
        SET pageSize = 50;  -- 默认每页50条
    END IF;

    -- 日期范围处理
    IF startDate IS NULL THEN
        SET startDate = DATE_SUB(CURDATE(), INTERVAL 7 DAY);
    END IF;
    
    IF endDate IS NULL THEN
        SET endDate = CURDATE();
    END IF;

    SELECT * FROM orders 
    WHERE order_date BETWEEN startDate AND endDate
    LIMIT pageSize;
END$$
DELIMITER ;

这种写法有三大优势:

  1. 完全兼容MySQL各版本(5.6+已验证)
  2. 默认值逻辑清晰可见
  3. 支持动态计算默认值(如基于当前日期)

3. C#调用时的异常捕获实战

使用MySqlConnector库(推荐替代官方驱动)时,特别注意参数传递方式:

public List<Order> GetOrders(DateTime? startDate, DateTime? endDate, int? pageSize)
{
    using var conn = new MySqlConnection("连接字符串");
    var cmd = new MySqlCommand("GetOrderList", conn) {
        CommandType = CommandType.StoredProcedure
    };

    // 参数处理三部曲
    cmd.Parameters.AddWithValue("startDate", startDate ?? (object)DBNull.Value);
    cmd.Parameters.AddWithValue("endDate", endDate ?? (object)DBNull.Value);
    cmd.Parameters.AddWithValue("pageSize", pageSize ?? (object)DBNull.Value);
    
    try 
    {
        conn.Open();
        using var reader = cmd.ExecuteReader();
        return MapOrders(reader); // 自定义映射方法
    }
    catch (MySqlException ex) when (ex.Number == 4500) 
    {
        // 专门捕获参数缺失错误
        Logger.Error($"参数缺失:{ex.Message}");
        throw new ArgumentException("必填参数未传递");
    }
    catch (Exception ex)
    {
        Logger.Error($"查询失败:{ex.Message}");
        throw;
    }
}

注意三个关键点:

  1. 使用DBNull.Value传递空值
  2. 捕获特定错误码45000系列
  3. 空值合并运算符的巧妙使用

4. 应用场景与避坑指南

典型应用场景

  1. 老系统迁移:从SQL Server迁移到MySQL时,参数默认值问题首当其冲
  2. 多数据库支持:需要保持存储过程在不同数据库中的行为一致
  3. 动态查询构建:根据参数是否为空动态调整查询条件

技术方案对比

方案 优点 缺点
参数判空设置默认值 灵活可控,兼容性好 每个参数都需要判断,代码冗余
应用程序设置默认值 业务逻辑集中管理 需要维护两套默认值逻辑
严格参数校验 避免意外行为 增加调用方的复杂度

避坑三原则

  1. 版本确认原则:不同MySQL版本对存储过程的支持有差异(建议使用5.7+)
  2. 参数校验原则:在存储过程开头添加:
IF pageSize <= 0 THEN
    SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'pageSize必须大于0';
END IF;
  1. 错误日志原则:在C#端记录详细错误日志:
catch (MySqlException ex)
{
    Logger.Error($"错误代码:{ex.Number} | 参数:{JsonConvert.SerializeObject(cmd.Parameters)}");
    throw;
}

5. 总结:优雅处理默认值问题

通过本文的探讨,我们可以总结出处理MySQL存储过程参数默认值的"三把钥匙":

  1. 参数验证双保险:存储过程内部做基础校验,应用程序做业务校验
  2. 异常处理策略:根据错误代码分层处理,45000系列专门处理业务逻辑错误
  3. 默认值声明统一:在数据库文档中明确标注参数的默认值规则

特别提醒跨数据库开发的团队:在编写存储过程时,建议创建参数规范文档,明确记录每个参数的:

  • 是否允许为空
  • 默认值逻辑
  • 取值范围限制
  • 关联的异常代码

这种规范不仅能避免参数传递错误,还能显著提高存储过程的可维护性。下次当你的存储过程报出参数数量错误时,不妨先检查是不是又手滑写了那个不存在的DEFAULT关键字?