Skip to content

MySQL - RESIGNAL

在 MySQL 存储过程中,健壮的错误处理对于创建可靠且可预测的应用程序逻辑至关重要。虽然简单的处理器可以捕获标准的 MySQL 错误,但 SIGNAL 和 RESIGNAL 语句使您能够创建和管理自己的自定义错误条件。

这使您能够直接在数据库中强制执行复杂的业务规则,并向客户端应用程序提供清晰、有意义的错误消息。

SIGNAL 用于引发自定义错误或警告。您可以定义 SQLSTATE 并提供自定义的 MESSAGE_TEXT。

RESIGNAL 用于错误处理程序内部。它允许您在执行一些逻辑(例如记录原始错误)之后,重新引发当前错误,或者引发一个不同的错误,可能带有更用户友好的消息。

-- 引发一个新的自定义错误
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Your custom error message';
-- 在处理程序内部,使用新消息重新引发错误
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
RESIGNAL SET MESSAGE_TEXT = 'An internal error occurred.';
END;

SQLSTATE ‘45000’ 是一个为用户定义异常保留的通用状态,使其成为自定义应用程序错误的安全选择。

让我们创建一个存储过程来下订单。它将检查产品的库存是否充足。如果不足,它将 SIGNAL 一个自定义错误。我们还将在一个通用处理程序中使用 RESIGNAL,为任何意外的数据库错误提供一个标准消息。

CREATE TABLE products (
product_id INT PRIMARY KEY,
name VARCHAR(100),
stock_level INT NOT NULL
);
INSERT INTO products VALUES (101, 'Super Widget', 20);
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT,
quantity INT
);
DELIMITER //
CREATE PROCEDURE place_order(IN p_product_id INT, IN p_quantity INT)
BEGIN
DECLARE current_stock INT;
-- 库存不足的自定义条件
DECLARE insufficient_stock CONDITION FOR SQLSTATE '45000';
-- 通用处理程序,用于捕获任何其他 SQL 错误并重新引发它
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
-- 您可以在重新引发错误之前在此处记录原始错误
RESIGNAL SET MESSAGE_TEXT = 'An unexpected error occurred while placing the order.';
END;
-- 开始事务
START TRANSACTION;
-- 检查当前库存水平
SELECT stock_level INTO current_stock FROM products
WHERE product_id = p_product_id FOR UPDATE;
IF current_stock < p_quantity THEN
-- 回滚并发出一个特定且用户友好的错误
ROLLBACK;
SIGNAL insufficient_stock
SET MESSAGE_TEXT = 'Insufficient stock for this product.';
ELSE
-- 更新库存并插入订单
UPDATE products SET stock_level = stock_level - p_quantity
WHERE product_id = p_product_id;
INSERT INTO orders (product_id, quantity) VALUES (p_product_id, p_quantity);
COMMIT;
END IF;
END //
DELIMITER ;

首先,一个成功的调用:

CALL place_order(101, 5);

这成功了,产品 101 的库存变为 15。现在,让我们尝试订购超出可用数量的产品:

CALL place_order(101, 25);

此调用将失败并返回我们的自定义错误:

ERROR 1644 (45000): Insufficient stock for this product.

在应用程序代码中处理自定义错误

Section titled “在应用程序代码中处理自定义错误”

客户端应用程序必须能够捕获这些自定义错误并向用户显示适当的消息。以下是使用各种语言实现的方法。

Node.js (async/await)
Python (mysql-connector)
Java (JDBC with try-with-resources)
PHP (PDO)
#### Node.js 与 `mysql2/promise`
捕获错误并检查其属性。
```javascript
// 假定 `place_order` 过程存在
const mysql = require('mysql2/promise');
async function attemptToPlaceOrder(productId, quantity) {
let connection;
try {
connection = await mysql.createConnection({ /* connection details */ });
await connection.query('CALL place_order(?, ?)', [productId, quantity]);
console.log(`Order placed successfully for ${quantity} of product ${productId}.`);
} catch (error) {
// 自定义错误位于 `error.message` 中
console.error(`下单失败: ${error.message}`);
// 您还可以检查特定的错误代码或 SQLSTATE
if (error.sqlState === '45000') {
console.log('这是一个业务规则违规(例如,库存不足)。');
}
} finally {
if (connection) await connection.end();
}
}
// 此调用将因我们的自定义错误而失败
attemptToPlaceOrder(101, 25);

使用 try...except 块捕获 mysql.connector.Error。

# 假定 `place_order` 过程存在
import mysql.connector
def attempt_to_place_order(product_id, quantity):
try:
with mysql.connector.connect(database='TUTORIALS', user='root', password='password') as cnx:
with cnx.cursor() as cursor:
cursor.callproc('place_order', (product_id, quantity))
print(f"Order placed for {quantity} of product {product_id}.")
cnx.commit()
except mysql.connector.Error as err:
# 自定义错误消息在 err.msg 中
print(f"下单失败: {err.msg}")
if err.sqlstate == '45000':
print("业务规则违规:库存不足。")
# 此调用将失败
attempt_to_place_order(101, 25)

捕获 SQLException 并检查其 SQLState。

// 假定 `place_order` 过程存在
import java.sql.*;
public class ResignalExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/TUTORIALS";
String user = "root";
String password = "password";
// 此调用将失败
String sql = "{CALL place_order(?, ?)}";
try (Connection conn = DriverManager.getConnection(url, user, password);
CallableStatement stmt = conn.prepareCall(sql)) {
stmt.setInt(1, 101); // product_id
stmt.setInt(2, 25); // quantity
stmt.execute();
System.out.println("Order placed successfully.");
} catch (SQLException e) {
System.err.println("下单错误: " + e.getMessage());
// 检查我们的自定义 SQLSTATE '45000'
if ("45000".equals(e.getSQLState())) {
System.err.println("这是一个自定义业务逻辑错误:库存不足。");
}
}
}
}

使用 try...catch 块包装调用,以捕获 PDOException。

<?php
// 假定 `place_order` 过程存在
$dsn = "mysql:host=127.0.0.1;dbname=TUTORIALS;charset=utf8mb4";
$options = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION];
try {
$pdo = new PDO($dsn, 'root', 'password', $options);
// 此调用将失败
$stmt = $pdo->prepare('CALL place_order(?, ?)');
$stmt->execute([101, 25]); // product_id, quantity
echo "Order placed successfully.\n";
} catch (PDOException $e) {
// 自定义消息在 $e->getMessage() 中
// errorInfo 数组包含 SQLSTATE
$errorInfo = $e->errorInfo;
echo "下单错误: " . $errorInfo[2] . "\n";
// 检查我们的自定义 SQLSTATE '45000'
if ($errorInfo[1] == 1644) { // SIGNAL/RESIGNAL 的 MySQL 错误代码
echo "这是一个业务规则违规。\n";
}
}
?>