背景

近期公司项目中加了一个积分机制,用户登录签到会获取登录积分,但会出现一种现象就是用户登录时会增加双倍积分,然后生成两个积分记录。此为问题 

问题分析

项目采用微服务架构,下图为积分机制流程

 
worker通过分析日志记录从而判断用户当天积分是否增加,进而进行积分增加增添记录或者无操作。
两个worker对积分数据库进行同时写入,造成积分双倍增加的情况,那问题找到了,就是对数据库并发写入的问题。
解决方法,加锁 

  • 共享锁

    • 定义:共享锁就是允许多个线程同时获取一个锁,一个锁可以同时被多个线程拥有。
    • 举例:比如有一个房间,你和你的女朋友都有要是可以进入这个房间,这就是共享锁,这个房间是你跟你的女朋友共享的 
  • 互斥锁 
    • 定义:互斥锁,也称作独占锁,排它锁,一个锁在某一时刻只能被一个线程占有,其它线程必须等待锁被释放之后才可能获取到锁。
    • 举例:就比如蹲厕所吧,同一时间一个茅坑由你一个人独占,这就叫做独占锁,互斥锁,其他人是不可以用的(当然,特殊情况除外)
  • 乐观锁

    • 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
  • 悲观锁 
    • 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

当然,这次解决问题采用的是悲观锁,互斥锁 
 

SQLAlchemy

项目是采用SQLAlchemy对mysql进行操作,题目是SQLAlchemy引发的思考,当然就少不了SQLAlchemy这个主角。SQLAlchemy 提供 with_for_update 函数 进行 锁的操作 .
session.Query(User).with_for_update().first()
session.Query(User).with_for_update(read=True).first()

完整形式为:

with_for_update(read=False, nowait=False, of=None)
read
是标识加互斥锁还是共享锁. 当为 True 时, 即 for share 的语句, 是共享锁. 多个事务可以获取共享锁, 互斥锁只能一个事务获取. 有"多个地方"都希望是"这段时间我获取的数据不能被修改, 我也不会改", 那么只能使用共享锁.
nowait
其它事务碰到锁, 是否不等待直接"报错".
of
指明上锁的表, 如果不指明, 则查询中涉及的所有表(行)都会加锁.

补充

在用SQLAlchemy对数据库操作的过程中出现这样一个现象,当在session.query()时,如果之前有session.add()操作,即使尚未进行commit操作,在query时也会查询到这个尚未真正插入数据库的对象。这个有待探究 ,后续补充
 
 
 
 

SQLAlchemy并发写入引发的思考的更多相关文章

  1. 【原创】记一次MySQL大表高并发写入引发CPU飙升的排障过程

    目录 一.故障现象... 1 二.初步分析... 2 三.排障过程... 2 1.排查是否QPS或insert并发请求上升导致问题发生... 2 2.排查是否锁资源等待或block导致了insert变 ...

  2. 曲演杂坛--一条DELETE引发的思考

    原文:曲演杂坛--一条DELETE引发的思考 场景介绍: 我们有一张表,专门用来生成自增ID供业务使用,表结构如下: CREATE TABLE TB001 ( ID ,) PRIMARY KEY, D ...

  3. Go select 死锁引发的思考

    Go select 死锁引发的思考 https://mp.weixin.qq.com/s/Ov1FvLsLfSaY8GNzfjfMbg一文引发的延续思考 上文总结 总结一 package main i ...

  4. 用读写锁三句代码解决多线程并发写入文件 z

    C#使用读写锁三句代码简单解决多线程并发写入文件时提示“文件正在由另一进程使用,因此该进程无法访问此文件”的问题 在开发程序的过程中,难免少不了写入错误日志这个关键功能.实现这个功能,可以选择使用第三 ...

  5. Spring之LoadTimeWeaver——一个需求引发的思考---转

    原文地址:http://www.myexception.cn/software-architecture-design/602651.html Spring之LoadTimeWeaver——一个需求引 ...

  6. C#使用读写锁三行代码简单解决多线程并发写入文件时线程同步的问题

    (补充:初始化FileStream时使用包含文件共享属性(System.IO.FileShare)的构造函数比使用自定义线程锁更为安全和高效,更多内容可点击参阅) 在开发程序的过程中,难免少不了写入错 ...

  7. 由SecureCRT引发的思考和学习

    由SecureCRT引发的思考和学习 http://mp.weixin.qq.com/s?__biz=MzAxOTAzMDEwMA==&mid=2652500597&idx=1& ...

  8. 解决一道leetcode算法题的曲折过程及引发的思考

    写在前面 本题实际解题过程是 从 40秒 --> 24秒 -->1.5秒 --> 715ms --> 320ms --> 48ms --> 36ms --> ...

  9. leveldb - 并发写入处理

    在并发写入的时候,leveldb巧妙地利用一个时间窗口做batch写入,这部分代码值得一读: Status DBImpl::Write(const WriteOptions& options, ...

随机推荐

  1. ansible.md

    ansible 测试环境配置 注意:192.168.100.201这台机器是主控机,剩下的192.168.100.202.192.168.100.203.192.168.100.210均为测试主机. ...

  2. 一般处理程序中用到session时

    一般处理程序ashx文件使用session 1.先引用System.Web.SessionState这个命名空间, 2.如果是要在HttpHandler中读取Session的内容,就要在实现IHttp ...

  3. 集合之HashMap

    HashMap也是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以key-value的形式存在.在HashMap中,key-value总是会当做一个整体来处理,系统会根据 ...

  4. VMware虚拟机更换根用户( su: Authentication failure问题)

    su命令不能切换root,提示su: Authentication failure,只要你sudo passwd root过一次之后,下次再su的时候只要输入密码就可以成功登录了.

  5. gitblit server windows搭建

    环境配置: windows64bit 系统 jdk1.8安装配置环境变量,这个不说了百度有 gitblit官网下载对应操作系统位数64bit包解压:http://www.gitblit.com/ ​ ...

  6. PAT乙级1007

    1007 素数对猜想 (20 分) 让我们定义d​n​​为:d​n​​=p​n+1​​−p​n​​,其中p​i​​是第i个素数.显然有d​1​​=1,且对于n>1有d​n​​是偶数.“素数对猜想 ...

  7. C语言程序设计I—第七周教学

    第七周教学总结(14/10-20/10) 教学内容 第二章 用C语言编写程序 2.5 生成乘方表和阶乘表 课前准备 在蓝墨云班课发布资源: PTA:2018秋第七周作业 分享码:FE065DC5D8C ...

  8. 关于Nanchtiy

    关于我 宇宙超级无敌蒟蒻一枚 爱玩游戏(LOL贼菜) 不爱刷题 不定时更新博客 福建某不知名市的某不知名高中的高二dog 偶尔看看动漫 欢迎加友链啦~ QQ:1468473741(会有妹子加吗?) 我 ...

  9. python为什么叫胶水语言?python为什么是系统脚本?

    python为什么叫胶水语言?python为什么是系统脚本?   特点是什么? python现在最广为闻名的形容大概有这些: 他是很好的胶水语言.什么是胶水语言?反正当时的我不知道. 他是新一代的系统 ...

  10. 笔记:long、longlong、int、float、NSString相互转换

    NSString *"; // 1.字符串转int int intString = [string intValue]; // 2.int转字符串 NSString *stringInt = ...