MySQL事务

时间:2020-05-13
本文章向大家介绍MySQL事务,主要包括MySQL事务使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

MySQL事务的介绍

1) 事务概念

一组mysql语句,要么执行,要么全不不执行。

2) 事务的特点

1原子性:一组事务,要么成功;要么撤回。

2稳定性 有非法数据(外键约束之类),事务撤回。

3隔离性事务独立运行。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度。

4可靠性:软、硬件崩溃后,InnoDB数据表驱动会利用日志文件重构修改。可靠性和高速度不可兼得, innodb_flush_log_at_trx_commit 选项 决定什么时候吧事务保存到日志里。

3) 事务控制语句

  • 开启事务:BEGINSTART TRANSACTION;显式地开启一个事务;

  • 提交事务:COMMIT;也可以使用COMMIT WORK,不过二者是等价的。COMMIT会提交事务,并使已对数据库进行的所有修改称为永久性的;

  • 回滚:ROLLBACK;有可以使用ROLLBACK WORK,不过二者是等价的。回滚会结束用户的务,并撤销正在进行的所有未提交的修改;

  • 保存节点:SAVEPOINT identifierSAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT

  • 删除事务:RELEASE SAVEPOINT identifier;删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;

  • 回滚到保存的节点:ROLLBACK TO identifier;把事务回滚到标记点;

4) mysql事务隔离级别

SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。

  • (1)Read Uncommitted(读取未提交内容)

  在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

  • (2)Read Committed(读取提交内容)

  这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。

  • (3)Repeatable Read(可重读)

       这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的幻影行。InnoDBFalcon存储引擎通过多版本并发控制(MVCCMultiversion Concurrency Control)机制解决了该问题。

  • (4)Serializable(可串行化)       

  这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:

  • 脏读(Drity Read)某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
  • 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。

  • 幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几行(Row)数据,而另一个事务却在此时插入了新的几数据,先前的事务在接下来的查询中,就会发现有几数据是它先前所没有的。

5) 设置mysql事务的隔离级别

打开mysql配置文件: sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf, 添加如下行。

保存配置文件,重启mysql服务。

sudo service mysql restart

django中使用事务

下面以天天生鲜项目中的订单提交为例,说明django中事务的使用:

from django.db import transaction
class OrderCommitView(View):
    '''订单创建'''

    @transaction.atomic  ##  使用事务
    def post(self,request):
        '''订单创建'''
        ##  判断用户是否登录
        user = request.user
        if not user:
            return JsonResponse({'res':0,'errmsg':'用户未登录'})

        ##  1、接收数据
        pay_method = request.POST.get('pay_method')
        addr_id = request.POST.get('addr_id')
        skus_id = request.POST.get('skus_id')
        ##  将skus_id的格式转化成列表形式
        skus_id = skus_id.split(',')


        ##  2、校验数据
        if not all([pay_method,addr_id,skus_id]):
            return JsonResponse({'res':1,'errmsg':'数据不完整'})
        skus = []
        for sku_id in skus_id:
            ##  遍历skus_id,从数据库中查询sku
            try:
                sku = GoodsSKU.objects.get(id=sku_id)
                skus.append(sku)
            except GoodsSKU.DoesNoExist:
                return JsonResponse({'res':2,'errmsg':'商品不存在'})
        try:
            addr = Address.objects.get(id=addr_id)
        except Address.DoesNoExist:
            return JsonResponse({'res':3,'errmsg':'地址不存在'})
        ##  校验支付方式
        if str(pay_method) not in OrderInfo.PAY_METHODS.keys():
            return JsonResponse({'res':4,'errmsg':'错误的支付方式'})
        ##  设置事务的节点
        save_id = transaction.savepoint()

        ##  3、业务处理
        ##########  (1)先向订单数据库中添加一条数据,准备数据:order_id、user、addr、pay_method、total_count、total_price、transit_price
        ##  计算total_count和total_price,先初始化为0,后面查询各个商品的数量和价格之后再进行累加,以及重新保存
        try:
            total_count = 0
            total_price = 0
            ##  生成order_id:格式:当前的时间+sku_id
            order_id = datetime.now().strftime('%Y%m%d%H%M%S') + str(user.id)
            ##  添加数据,创建新的订单数据
            order = OrderInfo.objects.create(order_id=order_id,
                                     user=user,
                                     addr=addr,
                                     pay_method=pay_method,
                                     total_count=total_count,
                                     total_price=total_price,
                                     transit_price=10)
    
            ##########  (2)向商品订单中添加多条数据,准备数据:order、sku、count、price
            conn = get_redis_connection('default')
            cart_key = 'cart_%d'%user.id
            for sku in skus:
                count = conn.hget(cart_key,sku.id)
                price = sku.price
                OrderGoods.objects.create(order=order,
                                          sku=sku,
                                          count=count,
                                          price=price)
                if sku.stock < int(count):
                    ##  业务失败时,回滚事务
                    transaction.rollback(save_id)
                    return JsonResponse({'res':6,'errmsg':'库存不足'})
                ##  MySQL数据库的库存减少,销量增加
                sku.stock -= int(count)
                sku.sales += int(count)
                sku.save()
                ##  删除Redis数据库中添加到订单中的数据
                conn.hdel(cart_key,sku.id)
                ##  累加total_count 和total_price
                total_count += int(count)
                total_price += int(count)*int(price)
            ##  重新更新订单中的total_count和total_price
            order.total_count = total_count
            order.total_price = total_price
            order.save()
        
        except Exception:
            ##  业务失败时,回滚事务
            transaction.rollback(save_id)
            return JsonResponse({'res':7,'errmsg':'订单创建失败'})

        ##  4、返回应答
        return JsonResponse({'res':5,'message':'创建订单成功'})

参考文档:数据库事务

原文地址:https://www.cnblogs.com/maoxinjueluo/p/12883162.html