高並發場景下,數據庫經常會發生數據重復插入的問題,這時候單單在插入前,查詢數據庫,判斷是否存在,再進行插入,往往不能保證數據唯一性。
查詢數據庫判斷是否存在
測試代碼: 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 行,這樣在並發下是不可行的。
分布式鎖方案
基於 redis
的 setnx
來解決這一問題。
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 value
若 value
存在 則返回 False
.
運行測試:
兩次插入,第一次插入10條,第二次插入0條。
每當插入前設置 UserName 的一個 redis Lock ,expire 設置為30s ,這樣就可以利用 setnx 的原子性 來實現分布式鎖來保證數據唯一性。
點贊 1