一.更新保证原子性
findOneAndUpdate 是获取mongo文档中第一条满足条件的数据并做修改。该函数是线程安全的,在多个线程中操作,不会对同一条数据进行获取修改,也就是该操作是原子操作。python实现demo如下:
# -*- coding: utf-8 -*-
# @Time : 2020/8/14 23:49
# @Author : CC
# @Desc : mongo_find_modify.py
from utils import mongo_util
collection_test = mongo_util.get_collection("test", "test")
result = collection_test.find_one_and_update({"userid": "2"}, {"$inc": {"rownum": 1}})
print(result)
mongodb4支持事物,能保证多条记录更新的原子性,详细pyhton操作demo如下:
from utils import mongo_util
collection_test = mongo_util.get_collection('test', 'test')
with mongo_util.client.start_session() as s:
s.start_transaction()
try:
doc1 = {"userid": 10}
collection_test.insert_one(doc1, session=s)
doc2 = {"userid": 12}
collection_test.insert_one(doc2, session=s)
raise Exception('测试抛出异常')
except:
s.abort_transaction()
raise
s.commit_transaction()
二.数据写入去重
但数据存储中经常会有保证单条记录唯一性的要求,简单说就是去重。实际情况有如下三种处理方案:
1.需要去重字段建立唯一索引
db.test.createIndex({"userid":1})
2.设置主键_id的值为 唯一字段的值,保存的时候如果有重复数据直接覆盖之前的数据
db.test.save({"userid": "5","age":21,"rownum": 1})
3.使用upsert=true去重
db.test.update({"userid": "5"}, {"$set":{"age":21},{"upsert":"true"})
方案1,如果表记录数大会增加额外的存储空间,并且在保存的时候容易忘记异常处理导致程序中断
方案2,没有方案一的缺点,但是会强制要求主键_id值设置为唯一字段的值;因为是全覆盖,容易导致保存的数据属性丢失
方案3,没有方案一和方案二的缺点,upsert操作会先在集合中进行数据查找,如果数据已经存在,则更新,否则才插入。数据的查找那就势必会使用索引,mongo索引用的是B树,时间复杂度为Olog(n),而没有索引的情况下则时间复杂度是O(n),数据量越大性能越慢
以上三种方案可以根据当前数据量来选择。如果数据量比较小选方案1和方案3均可,数据量比较大选择方案二
三.mongodb的锁保证操作原子性
1 锁粒度:
mongo3.0之后的默认引擎WiredTiger,锁的粒度最小是文档级别的
2 常用操作加入的锁:
insert:库级别的意向读锁(r),表级别的意向读锁(r),文档级别的读锁(R)
update:库级别的意向写锁(w),表级别的意向写锁(w),文档级别的写锁(W)
foreground方式创建索引:库级别的写锁(W)
background方式创建索引:库级别的意向写锁(w),表级别的意向写锁(w)
3 锁操作应用实例:
多台服务器(保证写入数据主键唯一性多种方案)
1 使用外部生成的唯一id作为主键,例如(基于uuid生成唯一的id),如果生成规则比较复杂可以提前批量生成不重复的主键id,用到的时候直接从主键集合取值
2 基于三方的redis作为分布式锁 SETNX key value