乐观并发检查失败 的问题

Hotus 2017-08-09 06:57:35
在ASP中,通过查询获取到一条数据,并标记为已获取

Set rs = Server.CreateObject("ADODB.Recordset")
rs.Open "Select top 1 * From List where used=0", conn,1,3
rs("Used")=1
rs.update

一条一条的访问,没什么问题,如果我在EXE程序中开100个线程,同时去获取就可能几个线程取到同一条,然后就出现并发检查失败的问题了

怎么避免多个线程同时取到同一条数据呢
...全文
512 13 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
吉普赛的歌 版主 2017-08-14
  • 打赏
  • 举报
回复
引用 12 楼 HotUs 的回复:
我现在暂时用的一次查询返回一批数据,减少访问的线程
嗯, 建议你转用 asp.net web form 或 .net mvc5 asp过时太久了, 建议你放弃
Hotus 2017-08-14
  • 打赏
  • 举报
回复
引用 11 楼 yenange 的回复:
#7 中的sql1要改一下(并发大时有问题),必须加锁还要加事务: 楼主如果看不懂C#代码也没关系, 你只需要取下面两种中的其中一种就可以了(建议方法二,可以严格保证而且无显式事务)。
--方法1
BEGIN TRAN
DECLARE @id INT 
SELECT TOP 1 @id=id FROM list WITH(ROWLOCK,UPDLOCK) WHERE USED=0 ORDER BY id ASC
UPDATE list SET USED=1 WHERE id=@id
SELECT @id
COMMIT TRAN
--方法2
SET ROWCOUNT 1
UPDATE list SET [used] = 1 
OUTPUT Deleted.id
WHERE [used]=0
下面的代码经50个并发验证没有问题。
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            string sql1 =
@"BEGIN TRAN
DECLARE @id INT 
SELECT TOP 1 @id=id FROM list WITH(ROWLOCK,UPDLOCK) WHERE USED=0 ORDER BY id ASC
UPDATE list SET USED=1 WHERE id=@id
SELECT @id
COMMIT TRAN
";
            string sql2 =
@"SET ROWCOUNT 1
UPDATE list SET [used] = 1 
OUTPUT Deleted.id
WHERE [used]=0";
            //测试两种方法,每次50个并发
            Console.WriteLine("方法1.");
            Parallel.For(0, 50, item => {
                ExecuteSQL(sql1);
            });
            Console.WriteLine("方法2.");
            Parallel.For(0, 50, item => {
                ExecuteSQL(sql2);
            });
            Console.Read();
        }

        private static void ExecuteSQL(string sql)
        {
            string connString = @"Data Source=(local)\sqlserver2014;Initial Catalog=AdventureWorks2014;Integrated Security=True";
            using (SqlConnection conn = new SqlConnection(connString))
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand(sql, conn);
                object r = cmd.ExecuteScalar();
                Console.WriteLine(r==null?"null":r.ToString());
            }
        }
    }
}
,我现在暂时用的一次查询返回一批数据,减少访问的线程
吉普赛的歌 版主 2017-08-10
  • 打赏
  • 举报
回复
#7 中的sql1要改一下(并发大时有问题),必须加锁还要加事务: 楼主如果看不懂C#代码也没关系, 你只需要取下面两种中的其中一种就可以了(建议方法二,可以严格保证而且无显式事务)。
--方法1
BEGIN TRAN
DECLARE @id INT 
SELECT TOP 1 @id=id FROM list WITH(ROWLOCK,UPDLOCK) WHERE USED=0 ORDER BY id ASC
UPDATE list SET USED=1 WHERE id=@id
SELECT @id
COMMIT TRAN
--方法2
SET ROWCOUNT 1
UPDATE list SET [used] = 1 
OUTPUT Deleted.id
WHERE [used]=0
下面的代码经50个并发验证没有问题。
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            string sql1 =
@"BEGIN TRAN
DECLARE @id INT 
SELECT TOP 1 @id=id FROM list WITH(ROWLOCK,UPDLOCK) WHERE USED=0 ORDER BY id ASC
UPDATE list SET USED=1 WHERE id=@id
SELECT @id
COMMIT TRAN
";
            string sql2 =
@"SET ROWCOUNT 1
UPDATE list SET [used] = 1 
OUTPUT Deleted.id
WHERE [used]=0";
            //测试两种方法,每次50个并发
            Console.WriteLine("方法1.");
            Parallel.For(0, 50, item => {
                ExecuteSQL(sql1);
            });
            Console.WriteLine("方法2.");
            Parallel.For(0, 50, item => {
                ExecuteSQL(sql2);
            });
            Console.Read();
        }

        private static void ExecuteSQL(string sql)
        {
            string connString = @"Data Source=(local)\sqlserver2014;Initial Catalog=AdventureWorks2014;Integrated Security=True";
            using (SqlConnection conn = new SqlConnection(connString))
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand(sql, conn);
                object r = cmd.ExecuteScalar();
                Console.WriteLine(r==null?"null":r.ToString());
            }
        }
    }
}
二月十六 版主 2017-08-10
  • 打赏
  • 举报
回复
引用 9 楼 HotUs 的回复:
[quote=引用 8 楼 sinat_28984567 的回复:]
BEGIN TRANSACTION
Select top 1 * From List where used=0 and Readed = 0
update table set Readed = 1
COMMIT TRANSACTION
使用这个,我要读取值该怎么做呢[/quote] 和普通的执行sql语句取值一样
Hotus 2017-08-09
  • 打赏
  • 举报
回复
引用 8 楼 sinat_28984567 的回复:
BEGIN TRANSACTION
Select top 1 * From List where used=0 and Readed = 0
update table set Readed = 1
COMMIT TRANSACTION
使用这个,我要读取值该怎么做呢
吉普赛的歌 版主 2017-08-09
  • 打赏
  • 举报
回复
1. 准备测试数据:
IF OBJECT_ID('list') IS NOT NULL DROP TABLE list
CREATE TABLE list(id INT PRIMARY KEY,[used] BIT NOT NULL DEFAULT(0))
INSERT INTO list(id)
SELECT number
FROM [master]..spt_values WHERE TYPE='p' AND number BETWEEN 1 AND 100

SELECT * FROM list


2. 在控制台做并发测试:
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;

namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
string sql1 =
@"declare @id int
SELECT TOP 1 @id=a.id from list AS a with(rowLOCK) where used=0
if @id is not null
begin
select @id
update list set used=1 where id=@id
end
";
string sql2 =
@"SET ROWCOUNT 1
UPDATE list SET [used] = 1
OUTPUT Deleted.id
WHERE [used]=0";
//测试两种方法,每次5个并发
Console.WriteLine("方法1.");
Parallel.For(0, 5,item=>{
ExecuteSQL(sql1);
});
Console.WriteLine("方法2.");
Parallel.For(0, 5, item => {
ExecuteSQL(sql2);
});
Console.Read();
}

private static void ExecuteSQL(string sql)
{
string connString = @"Data Source=(local)\sqlserver2014;Initial Catalog=AdventureWorks2014;Integrated Security=True";
using (SqlConnection conn = new SqlConnection(connString))
{
conn.Open();
SqlCommand cmd = new SqlCommand(sql, conn);
int r = Convert.ToInt32(cmd.ExecuteScalar());
Console.WriteLine(r);
}
}
}
}

Ginnnnnnnn 2017-08-09
  • 打赏
  • 举报
回复
你可以尝试用update output 的方式的方式去取数据,这样的话每次相当于是更新操作。不会重复取数据
顺势而为1 2017-08-09
  • 打赏
  • 举报
回复
引用 4 楼 appetizing_fish1 的回复:
楼主要达到什么样的控制目的呢 ?
你想控制用户取了一条数据后其它用户不能再获取这条记录, 那只有独占打开, 如果楼主想在修改存盘时控制,那可以用Timestamp
顺势而为1 2017-08-09
  • 打赏
  • 举报
回复
楼主要达到什么样的控制目的呢 ?
wdlglb 2017-08-09
  • 打赏
  • 举报
回复
你只能把所有的数据都查询出来,然后再并行处理,不然每个线程对SQLserver来说都是一个新的查询
OwenZeng_DBA 2017-08-09
  • 打赏
  • 举报
回复
引用 楼主 HotUs 的回复:
在ASP中,通过查询获取到一条数据,并标记为已获取 Set rs = Server.CreateObject("ADODB.Recordset") rs.Open "Select top 1 * From List where used=0", conn,1,3 rs("Used")=1 rs.update 一条一条的访问,没什么问题,如果我在EXE程序中开100个线程,同时去获取就可能几个线程取到同一条,然后就出现并发检查失败的问题了 怎么避免多个线程同时取到同一条数据呢
按照现在这种方式,多线程,肯定有可能取到同一条记录的。如果只是不想取到通一条,可以给查询加上个排他锁。不过此时也没法并行执行了,其实执行还是串行的。
OwenZeng_DBA 2017-08-09
  • 打赏
  • 举报
回复
引用 楼主 HotUs 的回复:
在ASP中,通过查询获取到一条数据,并标记为已获取 Set rs = Server.CreateObject("ADODB.Recordset") rs.Open "Select top 1 * From List where used=0", conn,1,3 rs("Used")=1 rs.update 一条一条的访问,没什么问题,如果我在EXE程序中开100个线程,同时去获取就可能几个线程取到同一条,然后就出现并发检查失败的问题了 怎么避免多个线程同时取到同一条数据呢
1.给每个记录加个ID,每次按照ID去取
二月十六 版主 2017-08-09
  • 打赏
  • 举报
回复
BEGIN TRANSACTION
Select top 1 * From List where used=0 and Readed = 0
update table set Readed = 1
COMMIT TRANSACTION

34,837

社区成员

发帖
与我相关
我的任务
社区描述
MS-SQL Server相关内容讨论专区
社区管理员
  • 基础类社区
  • 二月十六
  • 卖水果的net
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧