【Python】事务和锁

部署运行你感兴趣的模型镜像

1. 事务

InnoDB 引擎中支持事务,MyISAM不支持。

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `name` varchar(32) DEFAULT NULL,
  `amount` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

例如:A 给 B 转账100,那就会涉及2个步骤。

  • A账户 减100
  • B账户 加 100

这两个步骤必须同时完成才算完成,并且如果第一个完成、第二步失败,还是回滚到初始状态。

事务,就是来解决这种情况的。要成功都成功,要失败都失败。

事务的具有四大特性(ACID):

  • 原子性(Atomicity)

    原子性是指事务包含的所有操作不可分割,要么全部成功,要么全部失败回滚。
    
  • 一致性(Consistency)

    执行的前后数据的完整性保持一致。
    
  • 隔离性(Isolation)

    一个事务执行的过程中,不应该受到其他事务的干扰。
    
  • 持久性(Durability)

    事务一旦结束,数据就持久到数据库
    
1.1 MySQL客户端
mysql> select * from users;
+----+---------+---------+
| id | name    | amount  |
+----+---------+---------+
|  1 |    A    |    5    |
|  2 |    B    |    6    |
+----+---------+---------+
3 rows in set (0.00 sec)

mysql> begin;  -- 开启事务 start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update users set amount=amount-2 where id=1;   -- 执行操作
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update users set amount=amount+2 where id=2;   -- 执行操作
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;  -- 提交事务  rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from users;
+----+---------+---------+
| id | name    | amount  |
+----+---------+---------+
|  1 |    A    |    3    |
|  2 |    B    |    8    |
+----+---------+---------+
3 rows in set (0.00 sec)
mysql> select * from users;
+----+---------+---------+
| id | name    | amount  |
+----+---------+---------+
|  1 |    A    |    3    |
|  2 |    B    |    8    |
+----+---------+---------+
3 rows in set (0.00 sec)

mysql> begin; -- 开启事务
Query OK, 0 rows affected (0.00 sec)

mysql> update users set amount=amount-2 where id=1; -- 执行操作(此时数据库中的值已修改)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> rollback; -- 事务回滚(回到原来的状态)
Query OK, 0 rows affected (0.00 sec)

mysql> select * from users;
+----+---------+---------+
| id | name    | amount  |
+----+---------+---------+
|  1 |    A    |    3    |
|  2 |    B    |    8    |
+----+---------+---------+
3 rows in set (0.00 sec)
1.2 Python代码
import pymysql

conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db='userdb')
cursor = conn.cursor()

# 开启事务
conn.begin()

try:
    cursor.execute("update users set amount=1 where id=1")
    int('asdf')
    cursor.execute("update tran set amount=2 where id=2")
except Exception as e:
    # 回滚
    print("回滚")
    conn.rollback()
else:
    # 提交
    print("提交")
    conn.commit()

cursor.close()
conn.close()

2. 锁

在用MySQL时,不知你是否会疑问:同时有很多做更新、插入、删除动作,MySQL如何保证数据不出错呢?

MySQL中自带了锁的功能,可以帮助我们实现开发过程中遇到的同时处理数据的情况。对于数据库中的锁,从锁的范围来讲有:

  • 表级锁,即A操作表时,其他人对整个表都不能操作,等待A操作完之后,才能继续。
  • 行级锁,即A操作表时,其他人对指定的行数据不能操作,其他行可以操作,等待A操作完之后,才能继续。
MyISAM支持表锁,不支持行锁;
InnoDB引擎支持行锁和表锁。

即:在MyISAM下如果要加锁,无论怎么加都会是表锁。
   在InnoDB引擎支持下如果是基于索引查询的数据则是行级锁,否则就是表锁。

所以,一般情况下我们会选择使用InnoDB引擎,并且在 搜索 时也会使用索引(命中索引)。

接下来的操作就基于InnoDB引擎来操作:

CREATE TABLE `L1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

在InnoDB引擎中,update、insert、delete的行为内部都会先申请锁(排它锁),申请到之后才执行相关操作,最后再释放锁。

所以,当多个人同时向数据库执行:insert、update、delete等操作时,内部加锁后会排队逐一执行。

而select则默认不会申请锁。

select * from xxx;

如果,你想要让select去申请锁,则需要配合 事务 + 特殊语法来实现。

2.1 MySQL客户端示例

for update,排它锁,加锁之后,其他不可以读写。

begin; 
	select * from L1 where name="david" for update;    -- name列不是索引(表锁)
commit;
begin; -- 或者 start transaction;
	select * from L1 where id=1 for update;			  -- id列是索引(行锁)
commit;

2.2 Python代码

import pymysql
import threading


def task():
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8", db='userdb')
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    # cursor = conn.cursor()
	
    # 开启事务
    conn.begin()

    cursor.execute("select id,age from tran where id=2 for update")
    # fetchall      ( {"id":1,"age":10},{"id":2,"age":10}, )   ((1,10),(2,10))
    # {"id":1,"age":10}   (1,10)
    result = cursor.fetchone()
    current_age = result['age']
    
    if current_age > 0:
        cursor.execute("update tran set age=age-1 where id=2")
    else:
        print("已售罄")

    conn.commit()

    cursor.close()
    conn.close()


def run():
    for i in range(5):
        t = threading.Thread(target=task)
        t.start()


if __name__ == '__main__':
    run()

3. Django应用

3.1 事务,原子性操作

transaction.atomic() 是 Django 提供的一个上下文管理器,用于创建数据库事务。主要功能有如下两点:

  1. 原子操作: 确保在 transaction.atomic() 块中的所有数据库操作要么全部成功执行,要么出现异常全部回滚
  2. 事务管理: 自动处理事务的开始和结束
from django.db import transaction

with transaction.atomic():
	# 数据库操作A
    # 数据库操作B

3.2 锁 排它锁

在 Django 中,select_for_update() 用于在查询时对选中的数据库记录加上排他锁(exclusive lock),防止其他事务修改这些记录,直到当前事务结束(提交或回滚)时才释放锁。这通常用于避免并发操作带来的数据不一致问题。

from django.db import transaction

with transaction.atomic():
    customer_object = models.Customer.objects.filter(id=1).select_for_update().first()
	# 数据库操作A
    # 数据库操作B

3.3 注意事项

  1. 使用 select_for_update() 时必须确保数据库支持并启用了行级锁(如使用 InnoDB 引擎的 MySQL)。
  2. 该操作应在事务中进行,否则锁可能不会生效。
  3. 如果只是简单的删除操作,并且不需要考虑复杂的并发控制,不一定需要加锁;但如果涉及到多个操作(比如先查后删、或者需要确保数据一致性),则建议使用 select_for_update()。

您可能感兴趣的与本文相关的镜像

Python3.9

Python3.9

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

故林丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值