出于多种原因,我尽可能多地使用存储过程。
减少到数据库的往返次数
如果您需要一次更改多个相关表,那么您可以使用单个存储过程,以便只调用一次数据库。
明确定义业务逻辑
如果查询的某些事情必须是真实的,那么存储过程可以让了解 SQL(一种相当简单的语言)的人确保事情正确完成。
为其他程序员创建简单的界面
您的不具备 sql 能力的队友可以使用更简单的数据库接口,您可以确定他们不会在意外情况下将关系置于不良状态。
考虑:
SELECT a.first_name, IFNULL( b.plan_id, 0 ) AS plan_id
FROM account AS a
LEFT JOIN subscription AS s ON s.account_id = a.id
WHERE a.id = 23
相比:
CALL account_get_current_plan_id( 23 );
为它们编写一个漂亮的小包装器来处理存储过程调用,它们就开始工作了。
一次更新系统中的所有使用情况
如果每个人都使用存储过程来查询数据库,而您需要更改某些工作方式,则可以更新存储过程,只要不更改接口,它就会随处更新。
强制安全
如果您可以仅使用存储过程来完成系统内的所有操作,那么您可以为访问数据的用户帐户授予严格受限的权限。无需授予他们 UPDATE、DELETE 甚至 SELECT 权限。
简单的错误处理
很多人没有意识到这一点,但是您可以创建存储过程,从而使追踪大多数问题变得非常容易。
如果您使用良好的结构,您甚至可以集成您的代码库以正确处理返回的错误。
这是一个执行以下操作的示例:
- 对严重问题使用退出处理程序
- 对不太严重的问题使用 continue 处理程序
- 是否预先进行非表扫描验证
- 如果验证没有失败,接下来是否进行表扫描验证
- 如果事情验证,是否在事务中进行处理
- 出现问题时回滚所有内容
- 报告发现的任何问题
- 避免不必要的更新
这是一个组成的存储过程的内部结构,它接受一个帐户 ID、关闭帐户 ID 和一个 IP 地址,然后使用它们进行适当的更新。分隔符已经设置为 $$:
BEGIN
# Helper variables
DECLARE r_code INT UNSIGNED;
DECLARE r_message VARCHAR(128);
DECLARE it_exists INT UNSIGNED;
DECLARE n_affected INT UNSIGNED;
# Exception handler - for when you have written bad code
# - or something really bad happens to the server
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 0 as `id`, 10001 as `code`, CONCAT(r_message, ' Failed with exception') as `message`;
END;
# Warning handler - to tell you exactly where problems are
DECLARE CONTINUE HANDLER FOR SQLWARNING
BEGIN
SET r_code = 20001, r_message = CONCAT( r_message, 'WARNING' );
END;
SET r_code = 0, r_message = '', it_exists = 0, n_affected = 0;
# STEP 1 - Obvious basic sanity checking (no table scans needed)
IF ( 0 = i_account_id ) THEN
SET r_code = 40001, r_message = 'You must specify an account to close';
ELSEIF ( 0 = i_updated_by_id ) THEN
SET r_code = 40002, r_message = 'You must specify the account doing the closing';
END IF;
# STEP 2 - Any checks requiring table scans
# Given account must exist in system
IF ( 0 = r_code ) THEN
SELECT COUNT(id) INTO it_exists
FROM account
WHERE id = i_account_id;
IF ( 0 = it_exists ) THEN
SET r_code = 40001, r_message = 'Account to close does not exist in the system';
END IF;
END IF;
# Given account must not already be closed
# - if already closed, we simply treat the call as a success
# - and don't bother with further processing
IF ( 0 = r_code ) THEN
SELECT COUNT(id) INTO it_exists
FROM account
WHERE id = i_account_id AND status_id = 2;
IF ( 0 < it_exists ) THEN
SET r_code = 1, r_message = 'already closed';
END IF;
END IF;
# Given closer account must be valid
IF ( 0 = r_code ) THEN
SELECT COUNT(id) INTO it_exists
FROM account
WHERE id = i_updated_by_id;
END IF;
# STEP 3 - The actual update and related updates
# r-message stages are used in case of warnings to tell exactly where a problem occurred
IF ( 0 = r_code ) THEN
SET r_message = CONCAT(r_message, 'a');
START TRANSACTION;
# Add the unmodified account record to our log
INSERT INTO account_log ( field_list )
SELECT field_list
FROM account
WHERE id = i_account_id;
IF ( 0 = r_code ) THEN
SET n_affected = ROW_COUNT();
IF ( 0 = n_affected ) THEN
SET r_code = 20002, r_message = 'Failed to create account log record';
END IF;
END IF;
# Update the account now that we have backed it up
IF ( 0 = r_code ) THEN
SET r_message = CONCAT( r_message, 'b' );
UPDATE account
SET
status_id = 2,
updated_by_id = i_updated_by_id,
updated_by_ip = i_updated_by_ip
WHERE id = i_account_id;
IF ( 0 = r_code ) THEN
SET n_affected = ROW_COUNT();
IF ( 0 = n_affected ) THEN
SET r_code = 20003, r_message = 'Failed to update account status';
END IF;
END IF;
END IF;
# Delete some related data
IF ( 0 = r_code ) THEN
SET r_message = CONCAT( r_message, 'c' );
DELETE FROM something
WHERE account_id = i_account_id;
END IF;
# Commit or roll back our transaction based on our current code
IF ( 0 = r_code ) THEN
SET r_code = 1, r_message = 'success';
COMMIT;
ELSE
ROLLBACK;
END IF;
END IF;
SELECT
r_code as `code`,
r_message as `message`,
n_affected as `affected`;
END$$
状态码含义:
- 0:永远不应该发生 - 坏结果
- 1:成功 - 帐户已关闭或正确关闭
- 2XXXX - 逻辑或语法问题
- 3XXXX - 系统中出现意外数据值的问题
- 4XXXX - 缺少必填字段
与其信任不熟悉数据库(或根本不熟悉方案)的程序员,不如为他们提供接口要简单得多。
无需进行上述所有检查,他们可以简单地使用:
CALL account_close_by_id( 23 );
然后检查结果代码并采取适当的措施。
就个人而言,我相信如果您可以访问存储过程并且您没有使用它们,那么您真的应该考虑使用它们。