2 u010774511 u010774511 于 2014.05.13 13:15 提问

多线程编程中,2个线程同时调用一个存储过程…………

最近自学的时候遇到了这么一个问题……
要求是这样的:
1.在数据库中新建一个用户状态表。里面包含2个字段。user_status(userid bigint,status int)
userid为自增。
status默认为0;
插入1000条记录。

2.写一个存储过程,从user_status表中获取一条status=0的userid字段的值,取出userid时,需要同时把这条记录对应的status从0修改为1.

3.开启2个线程,在线程中循环调用第2步中的存储过程,直到user_status表中记录全部取完(即status全部为1)。要求:在2个线程中取出的userid不能重复。并把取出的userid,打印到某个文件中,一行一个userid

第一点,没啥问题吧应该,我在数据库里建好了。

第二点的话,我写的存储过程是:

create procedure sp_getuserid
as
declare @userid int
select top 1 @userid=userid from user_status where status=0 order by newid()
update user_status set status=1 where status=0 and userid =@userid
select userid from user_status where userid=@userid

第三点的代码:

public class Status implements Runnable {
    private int num;
    @Override
    public void run() {
        Boolean flag = true;
        while(flag)
        {
            flag=this.writeFile(this.getUserid());
        }
    }
    @SuppressWarnings("unchecked")
    public List<UserBean> getUserid() {
        // 创建数据库连接
        Connection conn = ConnectDb.Connect();
        QueryRunner qRunner = new QueryRunner();
        List<UserBean> list = new ArrayList<UserBean>();
        try {
            list = qRunner.query(conn, "exec sp_getuserid",
                    new BeanListHandler(UserBean.class));
        } catch (Exception e) {
            e.printStackTrace();
        }
        DbUtils.closeQuietly(conn);
        return list;
    }
    public Boolean writeFile(List<UserBean> list) {
        if (list.size()==0)
            return false;
        else {
            try {
                File file = new File("d:/test.txt");
                if (!file.exists())
                    file.createNewFile();
                FileOutputStream out = new FileOutputStream(file, true);
                for (int i = 0; i < list.size(); i++) {
                    StringBuffer sb = new StringBuffer();
                    SimpleDateFormat df = new SimpleDateFormat(
                            "yyyy-MM-dd HH:mm:ss:SSS");
                    sb.append(df.format(new Date()) + " \t"
                            + list.get(i).getUserid()+ " \t"+this.getNum());
                    sb.append(System.getProperty("line.separator"));
                    out.write(sb.toString().getBytes("utf-8"));
                }
                out.close();
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return true;
            }
        }
    }
    public static void main(String[] args) {
        new Thread(new Status(1)).start();
        new Thread(new Status(2)).start();
    }
    public Status(int num)
    {
        this.num=num;
    }

}

可是到最后,我不能在txt里得到1000条数据,总是缺少一些条目,但是数据库里却更新了……

如果我把存储过程改为

create procedure sp_getuserid
as
Begin TransAction
declare @userid int
declare @errno int
set @errno=0 
select top 1 @userid=userid from user_status where status=0 order by newid()
set @errno=@errno+@@error
update user_status set status=1 where status=0 and userid =@userid
set @errno=@errno+@@error
select userid from user_status where userid=@userid
set @errno=@errno+@@error
If @errno>0 
Begin 
rollback TransAction 
end 
Else
Begin 
Commit TransAction
End 

这样运行又会报死锁错误……
好吧,多线程的问题和存储过程不怎么会啊,求指教啊!!

2个回答

caodegao
caodegao   2014.05.13 14:09

我们在这样的情况是这样的
一:在表中新增一个job字段,这个job取序列号;
二:每次执行存储过程时先获取一个序列,并且写在要操作数据的那个表中;
三:做完步骤二的更新后用这个job查询出这个序列的数据进行业务;
四:做完业务后,将job的数据更新为空.

另外有个重置的行程,更新job在处理时有挂死或者超时的情况,比如你的job不为空,但是status一直还是0.过了一个小时还是这样,就必须重置了
这样每个job处理的数据全都是自己的数据.希望给你帮助

ldh911
ldh911   Rxr 2014.05.19 16:10

主要是存储过程写得不好,没有做事务控制,可以考虑这样:
1、存错过程:
在存储过程中使用游标,Select ... For Update,这样游标操作当前行时,可以锁定;然后利用游标来更新字段即可,最后也是返回游标的userid;记得用完了要关闭游标;

2、单句执行:
UPDATE user_status SET status=1
OUTPUT INSERTED.userid
WHERE userid = (
Select top 1 userid from user_status where status=0
)
要用PrepareStatement执行,注册好返回参数,具体请咨询Google。

Csdn user default icon
上传中...
上传图片
插入图片