前言

说起事务,后端工程师都不会陌生,这是存在于关系型数据库中的一个特性,它具有四个特性:

原子性
事务包含的所有操作要么都执行成功,要么都失败,不可分割
一致性
中间过程不管怎么执行,结果一定是一致的
隔离性
事务在执行过程中,不受其他事务的影响
持久性
执行成功之后,结果时永久修改的,不能撤回
MySQL执行的SQL是autocommit的,SALAlchemy 查询语句也是 autocommit的,就是说如果没有明确声明事务的begin,每个单独的SQL都是一个独立的事务。但是在做交易系统时,比如银行给用户A转账给用户B时,有两个操作,从A里面减100,然后给B加100。这两个操作必须放在一个事务里面才行,否是就会出现钱扣了,对方又没到账的情况。

所以,事务,是sqlalchemy乃至所有orm不可或缺的一环

开启事务

开启事务有两种方法,这里只说最简单的一种方法,就是利用with,为了更加真实的模拟业务场景,我们创建两张表和sqlalchemy的连接

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

connect = create_engine('mysql+pymysql://root:root@localhost:3306/test01')
ModelBase = declarative_base()


class User(ModelBase):
    # 定义表名
    __tablename__ = "user"
    # 声明字段
    id = Column(Integer, primary_key=True)
    name = Column(String(length=255))


class Role(ModelBase):
    __tablename__ = 'role'
    id = Column(Integer, primary_key=True)
    role = Column(String(length=255))
    
DBSession = sessionmaker(bind=connect,autoflush=False, autocommit=False, expire_on_commit=True)

然后我们利用一个fastapi框架来模拟一下

from fastapi import FastAPI, HTTPException, status
app = FastAPI()

@app.get('/')
async def create_sqlalchemy():
  # 使用上下文管理器开启事务
    with DBSession.begin() as session:
        data = {
            "id": 0,
            'name': 'Salmon'
        }
        # 添加user数据
        new_user = User(**data)
        session.add(new_user)
        data2 = {
            'id': 0,
          	# 'id':'添加错误数据'
            'role':'SuperUser1'
        }
        # 添加role数据
        new_role = Role(**data2)
        session.add(new_role)
        # 模拟错误
        if data2['role'] == 'SuperUser1':
          raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
    return 'ok'

需要知道的是,在事务开启后,使用with不需要自己来执行commit操作,执行完sql会自动提交,如果报异常会自动rollback 。

我们这里,主动抛出异常,两个表中的数据都没有改变。然后我们发送一个错误数据,不主动抛出,再来试试,解开那个id注释。然后把if和raise这两行删掉,然后,引发他原生错误。数据也不会发生改变。