最近一直在处理数据上传和采集的问题, 因为写在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_JOBID | string | 任务ID,主要用它来分割不同的任务 |
W_PROCESSTIME | date | 处理时间, 主要用在处理失败的或者未处理的, 超时10分钟后会强行再次被获取 |
ISUPLOAD | int | 是否已上传,上传成功后会更新此字段 |
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();
}