第6节,mysql.connector 如何使用事务

1. autocommit

在使用mysql.connector.connect 创建数据库连接时,有一个非常重要的参数autocommit,它的默认值是False。如果设置为True,进行DML操作则不需要commit

import mysql.connector

mydb = mysql.connector.connect(
    host="your ip",  # 数据库主机地址
    user="flink_user",  # 数据库用户名
    passwd="123456",  # 数据库密码
    port=6606,
    database='flink_db',
    autocommit=True
)

mycursor = mydb.cursor()
mycursor.execute("insert into city(name)values('北京')")
mycursor.execute("insert into city(name)values('上海')")

不执行mydb.commit(),这两条数据也能写进数据库,这样看起来方便和安全很多,不需要担心因为忘记commit而导致DML语句执行失效,可这样做性能会差很多。对于mysql而言,insert一条数据时其内部会创建一个事务,真正的insert操作是在事务内进行的,如果我们在执行大量insert操作时主动创建事务,那么这一组insert就是在一个事务内执行,减少了mysql创建事务的消耗。

2. 事务隐性开启

autocommit 默认为False,这意味着你必须执行commit方法来提交事务。第一条sql被执行时就会隐性的打开事务,commit方法被执行时,事务结束,对于数据库的DML操作都将生效,你可以使用in_transaction属性来查看数据库连接是否处于事务状态中

import mysql.connector

mydb = mysql.connector.connect(
    host="your ip",  # 数据库主机地址
    user="flink_user",  # 数据库用户名
    passwd="123456",  # 数据库密码
    port=6606,
    database='flink_db'
)

mycursor = mydb.cursor()
print(mydb.in_transaction)      # 没有开启事务   0
mycursor.execute("insert into city(name)values('北京')")
print(mydb.in_transaction)      # 已经开启事务   1
mycursor.execute("insert into city(name)values('上海')")
mydb.commit()
print(mydb.in_transaction)      # commit 之后,事务已经结束  0

3. 主动开启事务

使用start_transaction可以主动开启事务,该方法只有在autocommit设置为True时才能使用,事务一旦开启,只能使用commit或者rollback才能结束,在一个事务结束之前,不能开启新的事务。

4. rollback

事务里的操作,要么都成功,要么都失败,不能一部分成功,一部分失败,如果存在问题,需要使用rollback进行回滚,回滚到事务开启前的状态。

为了更好的演示事务的作用,我设计一个简单的实验

  1. 清空city表
  2. 开启事务,想city表里写入一条数据
  3. 查询city表里的数据数量,如果数量为1就抛出异常
  4. 捕获异常,执行rollback
  5. 检查city表里的数据量,应当为0

关于第3步,抛出异常的前提是业务层面上出现了错误,这里我简单的把业务错误设置成数据量为1,是为了简化实验,工作实践中要根据业务逻辑来决定是否抛出异常。比如银行转账,小明和小红转了1000元钱,那么需要有一个步骤检查小明的账号是不是减少了1000,小红的账号是不是增加了1000,如果这两个状态有一个不成立,都必须立即回滚。

下面是实验的代码

import mysql.connector

mydb = mysql.connector.connect(
    host="10.110.30.3",  # 数据库主机地址
    user="flink_user",  # 数据库用户名
    passwd="123456",  # 数据库密码
    port=6606,
    database='flink_db',
    autocommit=True
)


def add(city):
    mycursor = mydb.cursor()
    mycursor.execute(f"insert into city(name)values('{city}')")
    mycursor.close()

def get_count():
    mycursor = mydb.cursor()
    mycursor.execute("select count(1) as count from city")
    data = mycursor.fetchone()
    mycursor.close()
    if data[0] == 1:
        print(data, '抛出异常')
        raise Exception('抛出异常')


mydb.start_transaction()
try:
    add('北京')
    get_count()
    mydb.commit()
except:
    mydb.rollback()

我设置autocommit为True,为的是向你展示如何主动的开启一个事务,autocommit默认是0,不需要使用start_transaction主动开启事物,而是隐性的在执行第一条sql时开启事物。理解这段代码,有一个关键点,执行完add函数后,执行get_count函数前,如果你此时去数据库里查看city表会发现这张表是没有数据的,但是在执行get_count函数时,却可以查到数据。出现这个现象的原因是commit还没有被执行,事务没有结束,这意味着这些sql的执行结果对于你是不可见的,而对于处于事务状态中的sql语句,前面sql的执行结果却是可见的,即在select语句看来,前面的insert语句已经执行成功了,因此才能查到city表里的数据量为1。

第3步抛出异常后,立即进行回滚操作,这时你去数据库里看,city表里果真没有数据。

有人可能有些疑惑,为什么一定要执行rollback呢,get_count函数抛出异常,mydb.commit()语句不会被执行,事务不会被提交,city表里同样不会有数据写入,一切都还是事务开启前的状态。如果你是这样考虑的,那么显然,你忽略了一个关键,只有commit成功执行或者执行rollback,事务才会结束,如果不执行rollback,而后面又不小心的执行了commit,那一条数据就会写入到city表里。即便你后面没有执行commit,也不行,当前这个事务没有结束,你就不能开启新的事物。下面的代码就会犯这样的错误。

mydb.start_transaction()
try:
    add('北京')
    get_count()
    mydb.commit()
except:
    pass

mydb.commit()

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案