这篇文章的主要内容,来自与其他人的讨论。

  软件系统的开发或设计时,容易遇到有并发的情况。有时候需要刻意去避免,防止数据错误。比如超市卖商品,可能两个柜台同时卖出一款矿泉水,如果软件系统后台需要跟踪每个商品的库存,此时就需要特别考虑。如果两个柜台,同时采取"读当前库存,减一,得到最新库存,保存"的设计,则可能会导致数据错误。比如,两个柜台,读当前库存,都得到 100, 减一,都得到99,作为最新数据保存,保存99。最后,尽管同时卖出了两瓶矿泉水,最后系统的库存确是99。无疑是有问题的。

  一个简单的解决办法,就是再设计一个接口表。对于有可能并发的操作,统一插入一条"待处理的操作指令"到此接口表中,然后单独起一个线程,逐个处理此接口表中待处理数据。

  大致步骤如下:

1. 并发处理,统一插入一条待处理的操作指令到此接口表中,只 insert:
insert into ti_xxx ....; --process_flag = 0

2. 单独起一个线程,逐个读 : ti_xxx 中未处理的数据.
2.1
select top 1 from ti_xxx where process_flag = 0 order by increase_key,created_time;

2.2. insert/update 到 tt_xxx :
if exists(select 1 from tt_xxx where ....)
  update tt_xxx ....
else
  insert into tt_xxx...

2.3 更新 ti_xxx 数据为已处理:
update ti_xxx set process_flag = 1 where increase_key = xxx;

其中,ti_xxx 表使用自增长主键,或使用 uuid 做主键。

  如果只是单纯的超市软件系统,它的库存计算,其实不用很实时。让管理员人员,看当前时间的库存,与看5分钟之前的库存,从纯粹的管理层面,并没有大的区别。实际上,绝大多数系统,数据的实时性要求,都没有高到需要完全实时。另一方面,此类系统对数据的最终准确性,要求却是非常高的。比如,客户不太在意,9:05 分卖出一款矿泉水,只能在 9:10看到库存减少。但客户在意的是,9:05 分时刻卖出一款矿泉水,至少在下班后(21:00),能看到结果。

  如果我们将以上所述"单独起一个线程",做成每 0.5秒 运行一次的定时任务,则对于客户来说,完全看不到影响。

-------------------------------

2017/6/3 补充,(2017/6/4发现,以下测试步骤中有不当的地方,请忽略).

有人提到,可以用纯 SQL 来处理并发,使用适当的 lock 。但这样有时并不管用。比如按如下测试,则测试出问题:

测试环境: Windows 8.1 64位 + SQL Server 2014 Express.
测试步骤: 
step_1, 创建数据库 test_db1。

step_2, 运行 SQL 更改数据库属性:
ALTER DATABASE test_db1 SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE test_db1 SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE test_db1 SET READ_COMMITTED_SNAPSHOT ON;
ALTER DATABASE test_db1 SET MULTI_USER;

step_3,创建表,
CREATE TABLE [dbo].[Test](
[Id] [bigint] NULL,
[Name] [varchar](50) NULL,
[Counter] [bigint] NULL
) ON [PRIMARY];

step_4,创建存储过程:
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE PROCEDURE [dbo].[sp_test]
@Id [bigint],
@Name [varchar](50)

AS
BEGIN
BEGIN TRANSACTION
--先尝试更新记录占坑
UPDATE Test WITH(HOLDLOCK)
SET [Counter] = [Counter] + 1
WHERE Id = @Id;

WAITFOR DELAY '00:02:00';

--如果更新操作没有影响行,证明记录不存在,则插入
IF @@ROWCOUNT<1
BEGIN
INSERT Test
( Id, Name, [Counter] )
VALUES ( @Id, @Name, 1 );
END
COMMIT
END

GO

中间加了暂停。

step_5. 开两个 SQL Server Management studio, 分别运行 sp_test, 参数分别为:
step_5_1: 
id=1,
name='A',

step_5_2: 
id=1,
name='B',

step_6, 验证最后数据:
SELECT TOP 1000 * FROM [test_db1].[dbo].[Test];
得到两行数据:
Id Name Counter
1 A 2
1 B 1

结论:
纯SQL 代码不能起到期望的结果。

---------------------------------------------

2017/6/4 补充更正

重新测试,结论是 UPDATE...WITH(HOLDLOCK)... 可以锁住表的 update 操作,起到"并发时顺序处理"的期望结果。但并不需要使用 SET [Counter] = [Counter] + 1 这样的语句。

测试环境: Windows 8.1 64位 + SQL Server 2014 Express.
测试步骤: 
step_1, 创建数据库 test_db1。

step_2, 运行 SQL 更改数据库属性:
ALTER DATABASE test_db1 SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE test_db1 SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE test_db1 SET READ_COMMITTED_SNAPSHOT ON;
ALTER DATABASE test_db1 SET MULTI_USER;

step_3,创建表,
CREATE TABLE [dbo].[Test](
    [Id] [bigint] NULL,
    [Name] [varchar](50) NULL,
    [Counter0] [bigint] NULL,
    [created_time] [datetime] NULL,
    [updated_time] [datetime] NULL
) ON [PRIMARY];

step_4,创建存储过程:
CREATE PROCEDURE [dbo].[sp_test]
    @Id  [bigint],
    @Name [varchar](50)

AS
BEGIN
    BEGIN TRANSACTION
    --先尝试更新记录占坑
    print 'a0:'+ convert(varchar(255), getdate(), 121) + ','

UPDATE  Test WITH(HOLDLOCK)
    SET   --  [Counter] = [Counter] + 1, 
        Name=@Name, updated_time = getdate()
    WHERE   Id = @Id;

--需要在 WAITFOR DELAY 之前,将 @@ROWCOUNT 中的数值,暂时保存起来。因为 WAITFOR DELAY 之后,@@ROWCOUNT 中的数值会变。
    DECLARE @v_ROWCOUNT bigint
    set @v_ROWCOUNT = @@ROWCOUNT
    print 'a1:'+ convert(varchar(255), getdate(), 121)  + ',ROWCOUNT='+ cast( @v_ROWCOUNT as varchar(255))
    print 'a1.5:'+ convert(varchar(255), getdate(), 121)  + ',ROWCOUNT='+ cast( @@ROWCOUNT as varchar(255))

WAITFOR DELAY '00:00:20';

print 'a2:'+ convert(varchar(255), getdate(), 121) + ',ROWCOUNT='+ cast( @@ROWCOUNT as varchar(255))

--如果更新操作没有影响行,证明记录不存在,则插入
    IF @v_ROWCOUNT < 1
    BEGIN
        INSERT  Test
                ( Id, Name
                --, [Counter]
                ,created_time,updated_time )
        VALUES  ( @Id, @Name
            --, 1
            , getdate(), getdate() );
        print 'a3:'+ convert(varchar(255), getdate(), 121) + ',ROWCOUNT='+ cast( @@ROWCOUNT as varchar(255))
        
        WAITFOR DELAY '00:00:05';
    END
        print 'a4:'+ convert(varchar(255), getdate(), 121) 
        WAITFOR DELAY '00:00:02';
        print 'a4.5:'+ convert(varchar(255), getdate(), 121) 
    COMMIT
        print 'a5:'+ convert(varchar(255), getdate(), 121) 
END

GO

中间加了暂停。

step_5. 开两个 SQL Server Management studio, 分别运行 sp_test, 参数分别为:
step_5_1: 
id=1,
name='A',

step_5_2: 
id=1,
name='B',

step_6, 验证最后数据:
SELECT TOP 1000 * FROM [test_db1].[dbo].[Test];
得到一行数据:
Id    Name    Counter0    created_time    updated_time
1    B    NULL    2017-06-04 14:59:46.517    2017-06-04 14:59:53.520

从调试运行执行存储过程 SQL 的消息日志中,可以看到第二次存储过程的 update 的执行,确实是在第一次执行的 commit 之后。

结论:
UPDATE...WITH(HOLDLOCK)... 可以锁住表的 update 操作,起到"并发时顺序处理"的期望结果。但并不需要使用 SET [Counter] = [Counter] + 1 这样的语句。

很抱歉之前的错误结论,可能误导了一些朋友。

实测结果,数据库属性中,增加 :

ALTER DATABASE test_db1 SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE test_db1 SET READ_COMMITTED_SNAPSHOT ON;

可以在 UPDATE...WITH(HOLDLOCK)... 的数据库事务执行过程中,select 表 Test 数据。

而不加 ALLOW_SNAPSHOT_ISOLATION + READ_COMMITTED_SNAPSHOT,则此时 select 也堵塞。但 update Test 表都堵塞。

转载于:https://www.cnblogs.com/EasyLive2006/p/7630163.html

数据库存在即更新的高并发处理 - 转相关推荐

  1. 数据库存在即更新的并发处理 - 转

    前言 本节我们来讲讲并发中最常见的情况存在即更新,在并发中若未存在行记录则插入,此时未处理好极容易出现插入重复键情况,本文我们来介绍对并发中存在就更新行记录的七种方案并且我们来综合分析最合适的解决方案 ...

  2. 高并发处理之商品详情页

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 高并发处理之商品详情页 卜大伟 2019-01-18 11:13:47  2488  收藏  ...

  3. 伏威谈淘宝网的高并发处理与压力测试(转)

     其实到现在为止距离淘宝双十一事件已经过去蛮多天了,但在整个技术圈里面大家还是津津乐道.我这次在采访之前在和一些网友做沟通的时候,他们也提出了非常多非常有意思的问题,包括一些高并发的,一些压力测试的等 ...

  4. java 高并发商城库存订单处理,下单减库存,如何解决高并发减库存问题

    下单减库存,如何解决高并发减库存问题 1. 减库存 一般下单减库存的流程大概是这样的: 1.查询商品库存.这里直接查的Redis中的库存. 2.Redis中的库存减1.这里用到的Redis命令是:in ...

  5. php redis 高并发队列,laravel+Redis简单实现队列通过压力测试的高并发处理

    这篇文章主要介绍了关于laravel+Redis简单实现队列通过压力测试的高并发处理 ,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下 秒杀活动 在一般的网络商城中我们会经常接触到一些高 ...

  6. 分布式架构 高并发处理

    分布式架构 高并发处理 高并发介绍 在同时或者极短时间内,有大量请求到达服务端,每个请求都需要服务端耗费资源进行处理,并做出相应反馈 服务端比如同时开启进程数,能同时运行的线程数.网络连接数.CPU运 ...

  7. 服务器高并发处理/服务器宕机了怎么处理?

    服务器高并发处理/服务器宕机了怎么处理? 高并发问题是大部分服务器都经历过的,由于资源的有限性,其同时处理请求的能力自然也有限制.当高并发出现时,服务端的处理和响应速度会大幅降低,更严重的会使服务器崩 ...

  8. 伏威谈淘宝网的高并发处理与压力测试

    其实到现在为止距离淘宝双十一事件已经过去蛮多天了,但在整个技术圈里面大家还是津津乐道.我这次在采访之前在和一些网友做沟通的时候,他们也提出了非常多非常有意思的问题,包括一些高并发的,一些压力测试的等等 ...

  9. 政务数据放开在即 政策红利下大数据投资亮点凸显

    近期,<促进大数据发展三年工作方案(2016-2018)>.<促进大数据发展2016年工作要点>.<政务信息资源共享管理暂行办法>和<政务信息资源目录编制指南 ...

最新文章

  1. java读文件while改for循环_JAVA学习第十二天:while循环与for循环
  2. QTcreator 多线程(生产者消费者)
  3. exe4j打包exe_Java日常实用技巧之程序打包为可执行文件
  4. 用c语言求解n阶线性矩阵方程组,用C语言求解N阶线性矩阵方程Axb简单解法.docx
  5. 关于Infobright的一个小TIPS
  6. 联通定时休眠5G基站 戳破皇帝的新衣
  7. Linux 引导管理器 grub2 使用简介
  8. Encapsulate Field
  9. pcm转换在线工具_有木有好用的CAD格式转换工具可以推荐?在线等,挺急的
  10. 最大子段和之分治递归法
  11. css修改layui的下拉框样式 js_layui的安装以及简单操作
  12. Infosys:印度信息技术巨头公司
  13. 触屏查看大图(可以缩放)
  14. Jetpack 新成员 AndroidX App Startup 实践以及原理分析
  15. 仿苹果响应式官网(含代码!)
  16. Google SketchUp Cookbook: (Chapter 5) Roofs: Constraints and Inferences
  17. mac虚拟摄像头开发
  18. Tensorflow-gpu2.0.0安装【显卡型号:GTX1050 TI】
  19. Ubuntu 14.04 T430s 安装指纹识别
  20. 新加坡国立大学尤洋:我的四个选择,本质的喜欢催动长久的坚持丨青源专栏...

热门文章

  1. python 保存json时最后一个多了一个逗号_从json obj的最后一个对象中删除逗号
  2. 教你用BitMap排序、查找和存储大量数据
  3. php grid 分页,jqGrid实现前端分页
  4. 防火墙简单组网方案之双机热备
  5. 邀请函|2021 云原生实战峰会,邀请您免费现场参会报名
  6. 通过 IDE/Maven 部署 Serverless 应用实践
  7. springcloud配置文件上传大小_SpringCloud实战二-Nacos
  8. 使用什么优化器_优化器怎么选?一文教你选择适合不同ML项目的优化器
  9. html 关键帧作标记,关键帧有什么用?
  10. Oracle的ERP费用,Oracle ERP费用云