高并发场景下数据重复插入的问题

高并发场景下,数据库经常会发生数据重复插入的问题,这时候单单在插入前,查询数据库,判断是否存在,再进行插入,往往不能保证数据唯一性。

查询数据库判断是否存在

测试代码: th_insert_test.py 每次插入前,去数据库查询,要插入的 User0-9 是否存在,若不存在则插入,若存在,则返回已经有

#-*- coding:utf8 -*-

def db_op_thread_func(i, num_of_op):
    r = redis.Redis(host='127.0.0.1', port=6379, db=0)
    # r = redis.Redis(connection_pool=pool)

    conn = MySQLdb.connect(host="redisHost", port=3306, user="root", passwd="pass", db="blog")
    cursor = conn.cursor()

    for j in range(0, int(num_of_op)):
        nickname = 'User' + str(int(i % 10))
        lockkey = "lock"+nickname

        getsql = ("select ID from User  where Username = '%s'") % (nickname)
        cursor.execute(getsql)
        fetchData = cursor.fetchall()
        if not fetchData  : 
            sql = ("insert into User (Username)  values('%s')   ")%(nickname)
            cursor.execute(sql)
            id = int(conn.insert_id())
            print int(id)
            print "thread", i, ":", " num:", j
            conn.commit()
        else:
            print '已经有'

    conn.close()

if __name__ == "__main__":
        args = sys.argv
        num_of_thd = args[1]   ## 线程数
        num_of_op = args[2]    ## 每个线程的op数
        threads = []

        for i in range(0, int(num_of_thd)):
            threads.append(threading.Thread(target=db_op_thread_func, args=(i, num_of_op)))

        for t in threads:
            t.start()

        for t in threads:
            t.join()

测试一下,运行 python th_insert_test.py 50 5
50个线程,每个线程op数为5
这里写图片描述

理想的运行结果: User0-9 ,10条数据。
实际数据库插入运行结果:
结果1:
这里写图片描述
结果2:
这里写图片描述

可以看到 两次分别产生56 和46 行,这样在并发下是不可行的。

分布式锁方案

基于 redissetnx 来解决这一问题。

Python 代码:

def db_op_thread_func(i, num_of_op):
    r = redis.Redis(host='redisHost', port=6379, db=0)
    conn = MySQLdb.connect(host="dbHost", port=3306, user="root", passwd="pass", db="blog")
    cursor = conn.cursor()

    for j in range(0, int(num_of_op)):
        nickname = 'User' + str(int(i % 10))
        lockkey = "lock"+nickname

        getsql = ("select ID from User  where Username = '%s'") % (nickname)
        cursor.execute(getsql)
        fetchData = cursor.fetchall()

        reply = r.setnx(lockkey, 1)
        if (reply == True):
            r.expire(lockkey, 30)
            RedisLock =  False
        else:
            RedisLock =  True

        if not fetchData  and RedisLock == False:
            sql = ("insert into User (Username)  values('%s')   ")%(nickname)
            cursor.execute(sql)
            id = int(conn.insert_id())
            print int(id)
            print "thread", i, ":", " num:", j
            conn.commit()
        else:
            print '已经有'
    conn.close()

setnx key valuevalue 存在 则返回 False.
这里写图片描述

运行测试:
两次插入,第一次插入10条,第二次插入0条。
这里写图片描述

每当插入前设置 UserName 的一个 redis Lockexpire 设置为30s ,这样就可以利用 setnx 的原子性 来实现分布式锁来保证数据唯一性。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值