sqlalchemy如何优雅的实现线程安全的INSERT IF NOT EXIST?

数据库表mytable结构
id id_card
INT VARCHAR(32)
其中id是自增主键

如何优雅的实现if id_card 不存在就插入,当然要线程安全不能重复了。
我的实现方式是从代码层面加一个全局锁:

# 全局锁相关代码,这里没有加threading.lock,因为真实生产环境是多进程单线程模型。
if os.name != 'nt':
    import fcntl
class GlobalLock(object):
    def __init__(self, lock_file):
        self.lock_file = "/tmp/" + lock_file

    def acquire(self):
        if os.name != 'nt':
            self.fn = open(self.lock_file, "w")
            fcntl.flock(self.fn.fileno(), fcntl.LOCK_EX)

    def release(self):
        if os.name != 'nt':
            self.fn.close()


__gl__ = GlobalLock("__xmx__")


def global_lock(fp):
    def _d(*args, **kw):
        try:
            __gl__.acquire()
            r = fp(*args, **kw)
        finally:
            __gl__.release()
        return r

    return _d

数据库操作代码:

from sqlalchemy import func
from models.models import MyTable  # 自定义的数据库表Model类

def is_id_card_exists(id_card):
    try:
        # 注意这里一定要用scope_session,否则会由数据缓存,从而结果不准确
        s = db.create_scoped_session()
        result = s.query(MyTable).filter(func.upper(MyTable.id_card) == func.upper(id_card)).first()
        s.remove()
        return True if result is not None else False
    except Exception as e:
        logging.error("_app.dbhelper.py is_id_card_exists exception:" + str(e))
    return True

# 返回新mytable对象
@global_lock
def add_record(id_card):
    try:
        # 检查是否已存在
        if is_id_card_exists(id_card) is True:
            return None

        s = MyTable(name)
        db.session.add(s)
        db.session.commit()
        return s
    except Exception as e:
        logging.error("_app.dbhelper.py add_record exception:" + str(e))
    return None

我尝试过用MySQL直接锁表的方式,但是由于db.session缓存问题也是没能解决,有没有优雅的解决方案?当然可以把id_card字段设置为主键,但我认为这样不是解决问题,而是绕过问题。

阅读 9.1k
2 个回答

数据库上不加唯一键很容易出重复值的问题. 因为如果有一天你的代码是集群布署的,通过文件锁的方式是不太容易实现的,还涉及锁文件的管理问题(如突然中断后,锁文件没有清掉,会造成再也无法写入数据). 建议采用数据库的方式,如:

INSERT INTO table (id_card, name, age) VALUES(1, "A", 19) ON DUPLICATE KEY UPDATE    
name="A", age=19

找到了一个解决方法, 设置id_card为unique index, 这样如果有重复的id_card数据插入或者修改就会抛sql异常~~~ 完美解决了这个问题

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题