多线程 取数据必须不重复的方案

最近一直在处理数据上传和采集的问题, 因为写在asp.net 里面的web服务默认就是多线程的, 一个请求就是一个线程… 所以多线程之间为了不读取重复的数据, 就成了问题.

数据必须严格不重复, 同样的数据绝对不能处理2次…
多线程就更不能出现重复读取的现象了.

自己现在也用的是另外一套非常蹩脚的方法, . 把数据取出来然后在内存里面通过lock(object)的形式实现数据不重复处理的办法. 但是这又牵扯到数据的取出和更新, 也比较麻烦. 虽然实现了, 但是后续的修改和变更逻辑极其复杂.

后来再百度上又看了一遍找到了我认为最完美的方法,其它的我感觉都不怎么优美.
文章是下面这个.

找到了一种专门针对sqlserver的. 可以通过先更新同时通过deleted表(就像是在触发器中使用一样)取出的方式,来保证每条记录只会被读取一次。


declare @Rowid table(rowid int);
BEGIN
 set rowcount 100; --一次读取的行数
 --先将要读取的记录状态更新
 update Sms set [status]= 1  output deleted.ID into @Rowid Where [status] = 0;
 --读取刚更新状态的记录
 select  * from Sms where ID in (select Rowid from @Rowid);
END

但是这种只是针对sqlserver的, 所以在这个的基础上, 我设计改进了另外一种通用的方法.
同样是发短信为例,
逻辑过程如下

1. 开启事务, 保证update语句互不影响,
2. update top 100 Sms set status=@ThreadId where status = 0 ;
3. select  * from Sms where status = @ThreadId;
4. 提交事务

如果不想影响status的状态, 可以改成

1. 开启事务, 保证update语句互不影响,
2. update top 100 Sms set processer = @ThreadId where status = 0 ;
3. select  * from Sms where status = 0 and processer = @ThreadId;
4. 提交事务

这样就可以在这个请求中, 确保取到的数据,没有被其它线程取到. 因为每个线程的ThreadId肯定是不一样的.

当然这个逻辑也可以升级一下把ThreadId 改成其它的某个有规则的能够区分不同的任务的编号, 如果是分布式任务, 可以考虑前面再加个机器号.

或者把这个@ThreadId改成 orcale中的 sequence

如果是多线程只有一个程序在运行的话, 可以把这个数值通过单列模式在静态变量里面取数据.
每次任务执行前取一个 任务ID 当作@ThreadId.

我实际在用的代码

BBZQ表中加了3个字段

字段名类型说明
W_JOBIDstring任务ID,主要用它来分割不同的任务
W_PROCESSTIMEdate处理时间, 主要用在处理失败的或者未处理的, 超时10分钟后会强行再次被获取
ISUPLOADint是否已上传,上传成功后会更新此字段
    private static int _JobId = 0;
    public string GetJobId()
    {
        lock (SynCacheObject)
        {
            _JobId = _JobId + 1;
            return "JOB"+DateTime.Now.ToString("yyyyMMddHHmmssfff_")+_JobId;
        }
    }
public List<DataChangeLog> GetCHGAndWSWJobs()
        {
            //ReturnLogs rlog = new  ReturnLogs();
             
                var jobid = GetJobId(); //这里通过lock 锁定取得唯一的编号
                //AND(W_JOBID is null OR  W_PROCESSTIME < SYSDATE - 10 / 1440)-- 超时10分钟处理失败的或者未处理的的也会强行再次被获取,因为处理的部分有防止重复执行的功能, 所以可以重复执行
                //order by EXECUTEDATE asc   如果不按照事件发生时间排序,反审核之后又审核的的数据可能会被删掉
                //with temptable as (**) 临时表的写法是因为orcale 在 update 语句中 子查询不支持orderby  所以用了临时表.
                var sql = @"update BBZQ set W_JOBID = :JobId, W_PROCESSTIME=SYSDATE where JGID=2 AND XH in (
                                with temptable as(
				                        select XH
				                        from BBZQ 
				                        where ROWNUM < 200
				                        AND ( W_JOBID is null OR  W_PROCESSTIME < SYSDATE - 10/1440 )
				                        AND ISUPLOAD = 0 
				                        AND EXECUTEDATE > SYSDATE - 30 
				                        AND STATUS in(15, 16)   
				                        AND  JGID=2
				                        order by EXECUTEDATE asc
		                        ) 
                                select XH from temptable
                            )";

                var i =  DB.Execute(sql, new { JobId = jobid });
                if (i==0) //如果没有更新到数据也就直接返回了, 无需再次查询
                {
                    return null;
                }
                sql = @" SELECT *
                        FROM  BBZQ
                        WHERE W_JOBID = :JobId
                        ORDER BY EXECUTEDATE asc";
                //   throw new Exception(sql);
                return   DB.Query<DataChangeLog>(sql, new { JobId = jobid }).ToList();
                 
        }
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值