函数存储过程并发控制-案例
031-典型-存储过程并发控制
需求说明:为几个重要的生产材料生产编号。每日每个材料表产生不同的顺序编号。
多个生产材料,例如:CPU,内存,硬盘分别为C,M,H开头的编号+日期8位+顺序编号至少5位。要求每一个唯一,不能重复编号。
编号生成表:
use test;
create table pro_no(id bigint not null auto_increment primary key,type_name varchar(300),date_no varchar(300),max_no bigint);
alter table pro_no add unique index uniq_type_name_date_no(type_name,date_no);
生成编号函数如下:
delimiter &&
drop function if exists test.sf_pro_no &&
create function sf_pro_no(typeName varchar(300),dateNo varchar(8),noLen int)
returns varchar(255)
begin
declare error_code varchar(30) default '-1';
declare error_msg varchar(300) default '';
set @old_no = 0;
set @max_no = '-1';
if (typeName is null or typeName='') or (dateNo is null or dateNo='') or (noLen is null or noLen = '') then
set error_code = '-1';
set error_msg = '参数不能为空.';
set @max_no = concat('{',error_code,':',error_msg,'}');
elseif typeName not in ("CPU","MEMERY","HARD") then
set error_code = '-2';
set error_msg = 'typeName 必须是"CPU","MEMERY","HARD"之一.';
set @max_no = concat('{',error_code,':',error_msg,'}');
elseif length(dateNo) <> 8 then
set error_code = '-3';
set error_msg = '日期错误.';
set @max_no = concat('{',error_code,':',error_msg,'}');
elseif noLen <14 or noLen>100 then
set error_code = '-4';
set error_msg = '编号至少14位,最多100位.';
set @max_no = concat('{',error_code,':',error_msg,'}');
else
select ifnull(max(max_no),0)+1 into @old_no from pro_no where type_name = typeName and date_no = dateNo;
if @old_no = 1 then
insert into pro_no(type_name,date_no,max_no) values(typeName,dateNo,@old_no);
else
update pro_no set max_no = @old_no where type_name = typeName and date_no = dateNo;
end if;
set @max_no = concat(left(typeName,1),dateNo);
set @max_no = concat(@max_no,lpad(@old_no,noLen-length(@max_no),'0'));
set @max_no = concat('{0:',@max_no,'}');
#set @max_no = @old_no;
return @max_no;
end if;
end &&
delimiter ;
使用方法:
select sf_pro_no('CPU',date_format(now(),"%Y%m%d"),14);
注意的点:
1、ifnull在返回空记录的时候,不起作用。只能对值为空进行处理,因此需要使用max,使用min等也是可以的。
select ifnull(max(max_no),0)+1 into @old_no from pro_no where type_name = typeName and date_no = dateNo;
2、函数中无法使用事务,执行中出错,会导致数据不一致。(如上案例,只有一个更新,则不会,如果多次更新操作则会导致数据不一致)。
3、如上函数在并发请求的情况下,会返回重复的编号。存储过程同样。
存储过程,事务控制。 如下:
delimiter &&
drop procedure if exists test.sf_pro_no &&
create procedure sf_pro_no(in typeName varchar(300),in dateNo varchar(8),in noLen int,out maxNo varchar(300))
begin
declare error_code varchar(30) default '-1';
declare error_msg varchar(300) default '';
set @old_no = 0;
set @max_no = '-1';
if (typeName is null or typeName='') or (dateNo is null or dateNo='') or (noLen is null or noLen = '') then
set error_code = '-1';
set error_msg = '参数不能为空.';
set @max_no = concat('{',error_code,':',error_msg,'}');
elseif typeName not in ("CPU","MEMERY","HARD") then
set error_code = '-2';
set error_msg = 'typeName 必须是"CPU","MEMERY","HARD"之一.';
set @max_no = concat('{',error_code,':',error_msg,'}');
elseif length(dateNo) <> 8 then
set error_code = '-3';
set error_msg = '日期错误.';
set @max_no = concat('{',error_code,':',error_msg,'}');
elseif noLen <14 or noLen>100 then
set error_code = '-4';
set error_msg = '编号至少14位,最多100位.';
set @max_no = concat('{',error_code,':',error_msg,'}');
else
start transaction;
select ifnull(max(max_no),0)+1 into @old_no from pro_no where type_name = typeName and date_no = dateNo;
if @old_no = 1 then
insert into pro_no(type_name,date_no,max_no) values(typeName,dateNo,@old_no);
else
update pro_no set max_no = @old_no where type_name = typeName and date_no = dateNo;
end if;
commit;
set @max_no = concat(left(typeName,1),dateNo);
set @max_no = concat(@max_no,lpad(@old_no,noLen-length(@max_no),'0'));
set @max_no = concat('{0:',@max_no,'}');
select @max_no;
end if;
end &&
delimiter ;
使用方法:
call sf_pro_no('CPU',date_format(now(),"%Y%m%d"),14,@maxNo);
select @maxNo;
测试代码:
#encoding:utf-8
#Author:lzj
#Date:2024-07-12
#Description:简单并行SQL执行。
import concurrent.futures
import pymysql
def connect():
config = {
'host': '127.0.0.1',
'port': 3306,
'user': 'root',
'passwd': 'mysql',
'charset': 'utf8mb4',
'db':'test'
#'cursorclass': pymysql.cursors.DictCursor
}
conn = pymysql.connect(**config)
cursor = conn.cursor()
return conn,cursor
def call_max_no(index):
conn,cur = connect()
#conn.autocommit(1)
#sql = "select sf_pro_no('CPU',date_format(now(),'%Y%m%d'),14); "
sql = "call sf_pro_no('CPU',date_format(now(),'%Y%m%d'),14,@maxNo);"
cur.execute(sql)
data = cur.fetchall()
print(data)
cur.close()
conn.close()
def main():
#多进程
#with concurrent.futures.ProcessPoolExecutor(max_workers=10) as exector:
#多线程
with concurrent.futures.ThreadPoolExecutor(max_workers = 10) as exector:
futures = [exector.submit(call_max_no,i) for i in range(150)]
#concurrent.futures.wait(futures)
if __name__ == '__main__':
main()
执行100次,最大并发10个线程或进程,都会产生重复编号。
============== 那如何解决函数,存储过程的并发问题呢 ================
总结:
1、函数
由于无法使用事务控制语句。也无法设置autocommit=1,因此需要在代码会话设置(很多框架默认autocommit=0),数据库也可以设置,但不建议,会影响其他事务提交。
在第一条select开始,,添加for update添加ix意向排它锁,不能添加lock in share mode的is共享锁,不会阻塞其他读。
select ifnull(max(max_no),0)+1 into @old_no from pro_no where type_name = typeName and date_no = dateNo for update;
2、存储过程
显式开启事务,start transcation; commit; 很多框架默认autocommit=0。也可以在代码会话设置autocommit=1,不显式开启事务。
在第一条select开始,,添加for update添加ix意向排它锁,不能添加lock in share mode的is共享锁,不会阻塞其他读。
select ifnull(max(max_no),0)+1 into @old_no from pro_no where type_name = typeName and date_no = dateNo for update;
则都能安全的更新,但都会导致锁等待,相当于串行执行。
特别提醒,pro_no表,添加索引或唯一索引,最好是唯一索引。 如果多行则更新多行,产生更多的执行开销,锁等待。
alter table pro_no add unique index uniq_type_name_date_no(type_name,date_no);
有索引能保证update时行锁,否则则会表锁。
测试代码,在执行次数,并发小的情况下,不添加for update,IX锁,满足条件1,则可能不会产生重复编号,因为案例只有一个select insert/update,有索引执行非常快。 执行量,并发量增加则更容易验证出来是否有重复编号。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!