存储过程介绍以及使用
存储过程
存储过程是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程。
适用场景
存储过程是数据库管理系统中的一个重要功能,适用于多种场景,主要优势在于提高性能、安全性和维护性。以下是存储过程的常见使用场景:
- 频繁执行的复杂业务逻辑
当某个业务逻辑需要频繁执行且涉及多个SQL操作时,存储过程可以减少网络传输和SQL解析开销。比如,银行转账操作需要更新多个账户余额,存储过程可以封装这一系列操作。 - 数据一致性和事务管理
存储过程支持事务处理,确保一系列操作要么全部成功要么全部回滚。
适用于需要强一致性的场景,比如订单处理、库存管理等。 - 权限控制和安全性提升
通过存储过程,可以限制用户直接访问底层表,仅允许执行特定操作,降低数据泄露或误操作的风险。比如,用户只能通过存储过程修改数据,而不能直接执行update语句。 - 减少网络流量
对于复杂的查询或者批量操作,存储过程在数据库服务器端执行,只需要传输调用指令和结果,减少客户端与服务器之间的数据传输量。比如,高并发或网络带宽有限的场景。 - 代码复用和维护便利
存储过程将常用逻辑封装为模块,多个应用可以重复调用,避免代码冗余。当业务逻辑变更时,只需要修改存储过程,无需更新所有客户端代码。 - 批量数据处理
对于大量的数据插入、更新和删除,存储过程可以优化执行效率,减少锁竞争和日志开销。比如,数据迁移、报表生成等场景。 - 性能优化
数据库通常会对存储过程进行预编译和缓存,提高执行效率。复杂的计算或数据处理在服务器端完成,充分利用数据库引擎的性能。 - 定时任务和自动化
结合数据库的作业调度功能,存储过程可以用于定时执行数据清理、备份等任务。
不适用场景
简单查询:若仅需执行单条SQL语句,直接使用SQL更高校。
高度动态的逻辑:如果业务需要频繁变化,存储过程的修改和部署可能不如应用层灵活。
跨数据库兼容性:存储过程的语法通常与特定数据库系统绑定,不利于迁移。
综上所述,存储过程适用于封装复杂、稳定且频繁执行的数据库操作,但在灵活性和可移植性要求高的场景下需谨慎使用。
使用存储过程的原因
存储过程说白了就是一堆SQL 的合并,中间加了点逻辑控制。存储过程处理比较复杂的业务时比较实用。具体分为两个方面:
①响应时间上来说有优势:如果你在前台处理的话。可能会涉及到多次数据库连接。但如果你用存储过程的话,就只有一次。存储过程可以给我们带来运行效率提高的好处;
②从安全上使用了存储过程的系统更加稳定:程序容易出现BUG 不稳定,而存储过程,只要数据库不出现问题,基本上是不会出现什么问题的。
存储过程的优势
优势主要体现在:
① 存储过程只在创建时进行编译,以后每次执行存储过程都不需再重新编译,而一般SQL 语句每执行一次就编译一次所以使用存储过程可提高数据库执行速度。
② 当对数据库进行复杂操作时(如对多个表进行增删改查时)可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用。这些操作,如果用程序来完成,就变成了一条条的SQL 语句,可能要多次连接数据库。而换成存储,只需要连接一次数据库就可以了。
③ 存储过程可以重复使用可减少数据库开发人员的工作量。
④ 安全性高可设定只有某此用户才具有对指定存储过程的使用权。
⑤ 更强的适应性:由于存储过程对数据库的访问是通过存储过程来进行的,因此数据库开发人员可以在不改动存储过程接口的情况下对数据库进行任何改动,而这些改动不会对应用程序造成影响。
⑥ 分布式工作:应用程序和数据库的编码工作可以分别独立进行,而不会相互压制。
实际使用
项目上遇到过一个需求人员画像模型任务管理模块,当新增人员画像模型任务或者编辑模型任务操作成功时候都需要保存一下当前季度的标签和赋分配置内容。每个季度的标签和赋分配置内容有且只能有一份。保存逻辑是:①删除人员画像模型任务关联标签表中当前季度的标签配置;②删除人员画像模型任务关联赋分项表中当前季度的赋分配置;③获取最新的标签配置信息存入员画像模型任务关联标签表;④获取最新赋分配置存入人员画像模型任务关联赋分项表中等四个步骤。如果每次都这样频繁的操作数据库导致接口耗时25秒,用户体验感不好,于是采用存储过程进行优化,优化完以后接口调用耗时只需要138ms,效率得到了极大的提升。
java
private BaseResult handleAddTask(HumanModelTaskManageParam param) {
HumanModelTaskManageInfo humanModelTask = new HumanModelTaskManageInfo();
//根据当时时间设置时间和季度
param.setModelPeriodYear(DateUtil.getCurrentYear());
param.setModelPeriodQuarter(DateUtil.getCurrentQuarter());
try {
BeanUtils.copyProperties(param, humanModelTask);
// 立即执行任务的状态检查
if (Constants.TaskExecuteType.EXECUTE_NOW.getCode().equals(humanModelTask.getExecuteTimeType())) {
Long executingCount = humanModelTaskManageInfoMapper.selectCount(
new QueryWrapper<HumanModelTaskManageInfo>()
.eq("execute_status", "1") // 执行中状态
);
if (executingCount > 0) {
return BaseResult.error(ERR_MODEL_TASK_EXECUTE_EXIST.getCode(), ERR_MODEL_TASK_EXECUTE_EXIST.getMsg());
}
humanModelTask.setExecuteTime(new Date());
humanModelTask.setExecuteStatus("1");
} else if (Constants.TaskExecuteType.EXCUTE_SCHEDULE.getCode().equals(humanModelTask.getExecuteTimeType())) {
humanModelTask.setExecuteStatus("0");
}
humanModelTask.setExecuteHumanName(param.getUserId());
humanModelTask.setCreateTime(new Date());
humanModelTask.setUpdateTime(new Date());
int insert = humanModelTaskManageInfoMapper.insert(humanModelTask);
if (insert == 1) {
LOGGER.info("任务新增成功,ID={}, 开始保存配置", humanModelTask.getId());
// saveTaskRelatedConfigs(humanModelTask.getId(), humanModelTask.getModelPeriodYear(), humanModelTask.getModelPeriodQuarter());
saveTaskRelatedConfigStoredProcedures(humanModelTask.getId(), humanModelTask.getModelPeriodYear(), humanModelTask.getModelPeriodQuarter());
// 如果是立即执行,启动异步任务
if (Constants.TaskExecuteType.EXECUTE_NOW.getCode().equals(humanModelTask.getExecuteTimeType())) {
executeTaskAsync(humanModelTask);
}
return BaseResult.success("新增成功");
} else {
return BaseResult.error(ERR_SAVE_OR_UPDATE_HUMAN_MODEL_TASK_MANAGE.getCode(), "新增失败");
}
} catch (Exception e) {
LOGGER.errorWithErrorCode(ERR_SAVE_OR_UPDATE_HUMAN_MODEL_TASK_MANAGE.getCode(), "新增人员画像模型任务失败", e);
return BaseResult.error(ERR_SAVE_OR_UPDATE_HUMAN_MODEL_TASK_MANAGE.getCode(), ERR_SAVE_OR_UPDATE_HUMAN_MODEL_TASK_MANAGE.getMsg());
}
}
原始数据库保存方式:需要多次操作数据库
java
/**
* 保存当前任务关联的标签和评分配置快照
*/
public void saveTaskRelatedConfigs(Long taskId, Integer year, Integer quarter) {
// 删除旧的关联关系(确保每个任务只有一份最新配置)
humanModelTaskRelatedLabelMapper.delete(
new QueryWrapper<HumanModelTaskRelatedLabel>().eq("model_period_year", year)
.eq("model_period_quarter", quarter)
);
humanModelTaskRelatedScoringMapper.delete(
new QueryWrapper<HumanModelTaskRelatedScoring>()
.eq("model_period_year", year)
.eq("model_period_quarter", quarter)
);
// 获取最新的标签配置
QueryWrapper<LableManageInfo> queryWrapper = new QueryWrapper<>();
List<LableManageInfo> labels = lableManageInfoMapper.selectList(queryWrapper);
if(CollectionUtils.isNotEmpty(labels)){
for (LableManageInfo label : labels) {
HumanModelTaskRelatedLabel relatedLabel = new HumanModelTaskRelatedLabel();
BeanUtils.copyProperties(label,relatedLabel);
relatedLabel.setHumanModelTaskId(taskId);
relatedLabel.setModelPeriodYear(year);
relatedLabel.setModelPeriodQuarter(quarter);
relatedLabel.setSort(label.getSort());
relatedLabel.setCreateTime(new Date());
relatedLabel.setUpdateTime(new Date());
humanModelTaskRelatedLabelMapper.insert(relatedLabel);
}
}
// 获取最新的评分配置
QueryWrapper<ScoringModelDict> scoringqueryWrapper = new QueryWrapper<>();
List<ScoringModelDict> scorings = scoringModelDictMapper.selectList(scoringqueryWrapper);
if(CollectionUtils.isNotEmpty(scorings)){
for (ScoringModelDict scoring : scorings) {
HumanModelTaskRelatedScoring relatedScoring = new HumanModelTaskRelatedScoring();
BeanUtils.copyProperties(scoring,relatedScoring);
relatedScoring.setHumanModelTaskId(taskId);
relatedScoring.setModelPeriodYear(year);
relatedScoring.setModelPeriodQuarter(quarter);
relatedScoring.setCreateTime(new Date());
relatedScoring.setUpdateTime(new Date());
humanModelTaskRelatedScoringMapper.insert(relatedScoring);
}
}
}
存储过程方式:只需要操作一次数据库就行
java
@Async
public void saveTaskRelatedConfigStoredProcedures(Long taskId, Integer year, Integer quarter) {
Date start = new Date();
LOGGER.info("执行人员画像模型保存赋分、标签存储过程开始...");
LOGGER.info("人员画像模型保存赋分、标签存储过程传入taskId:{},year:{},quarter:{}",taskId, year, quarter);
jdbcTemplate.update(
"CALL public.save_task_related_configs(?::bigint, ?::integer, ?::integer)",
taskId,
year,
quarter);
Date end = new Date();
LOGGER.info("执行人员画像模型保存赋分、标签存储过程耗时:{}",end.getTime()-start.getTime());
}
存储过程sql:
```java
-- 删除已有同名过程
DROP PROCEDURE IF EXISTS save_task_related_configs(BIGINT, INTEGER, INTEGER);
-- 创建新的存储过程
CREATE OR REPLACE PROCEDURE save_task_related_configs(
IN p_task_id BIGINT,
IN p_year INTEGER,
IN p_quarter INTEGER
)
AS $$
BEGIN
-- 删除旧标签配置
DELETE FROM t_human_model_task_related_label
WHERE model_period_year = p_year AND model_period_quarter = p_quarter;
-- 插入新标签配置
INSERT INTO t_human_model_task_related_label (
human_model_task_id,
model_period_year,
model_period_quarter,
label_type,
first_level_label_code,
first_level_label_name,
second_level_label_code,
second_level_label_name,
third_level_label_code,
third_level_label_name,
sort,
create_time,
update_time
)
SELECT
p_task_id,
p_year,
p_quarter,
label_type,
first_level_label_code,
first_level_label_name,
second_level_label_code,
second_level_label_name,
third_level_label_code,
third_level_label_name,
sort,
NOW(),
NOW()
FROM label_manage_info;
-- 删除旧评分配置
DELETE FROM t_human_model_task_related_scoring
WHERE model_period_year = p_year AND model_period_quarter = p_quarter;
-- 插入新评分配置
INSERT INTO t_human_model_task_related_scoring (
human_model_task_id,
model_period_year,
model_period_quarter,
model_type,
scoring_type,
scoring_code,
scoring_name,
scoring_num,
relate_rlx_label_code,
relate_clx_label_code,
relate_wfxw_label_code,
relate_wfflfj_dict_code,
create_time,
update_time
)
SELECT
p_task_id,
p_year,
p_quarter,
model_type,
scoring_type,
scoring_code,
scoring_name,
scoring_num,
relate_rlx_label_code,
relate_clx_label_code,
relate_wfxw_label_code,
relate_wfflfj_dict_code,
NOW(),
NOW()
FROM scoring_model_dict;
END;
$$ LANGUAGE plpgsql;
相比原始多次调用数据库方式,存储过程一次性调用数据库更加简单一点。
存储过程简单理解,以餐厅点餐为例:
没有使用存储过程:每次点菜都需要跟服务员说一下,服务员每次都需要给后厨传达,效率较低。
使用存储过程:直接点套餐,相当于调用存储过程,服务员一次性把整个套餐流程交给后厨,后厨按照固定步骤,快速出餐。