15 Django事务和锁应用
1 Django中的事务锁
-
事务(MySQL+innodb)
多个数据库操作,要成功都成功,要失败都失败。
from django.db import transaction with transaction.atomic(): # 数据库操作A # 数据库操作B
-
数据库锁(结合事务)
from django.db import transaction with transaction.atomic(): # 锁,select_for_update()数据库中的排他锁 customer_object = models.Customer.objects.filter(id=1).select_for_update().first() # 数据库操作A # 数据库操作B
事务什么时候结束,锁什么时候结束。
2 事务
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;
例如:李杰 给 武沛齐 转账 100,那就会涉及2个步骤。
- 李杰账户 减100
- 武沛齐账户 加 100
这两个步骤必须同时完成才算完成,并且如果第一个完成、第二步失败,还是回滚到初始状态。
事务,就是来解决这种情况的。 大白话:要成功都成功;要失败都失败。
事务的具有四大特性(ACID):
-
原子性(Atomicity)
原子性是指事务包含的所有操作不可分割,要么全部成功,要么全部失败回滚。
-
一致性(Consistency)
执行的前后数据的完整性保持一致。
-
隔离性(Isolation)
一个事务执行的过程中,不应该受到其他事务的干扰。
-
持久性(Durability)
事务一旦结束,数据就持久到数据库
2-1 MySQL客户端
mysql> select * from users;
+----+---------+---------+
| id | name | amount |
+----+---------+---------+
| 1 | wupeiqi | 5 |
| 2 | alex | 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 | wupeiqi | 3 |
| 2 | ale x | 8 |
+----+---------+---------+
3 rows in set (0.00 sec)
mysql> select * from users;
+----+---------+---------+
| id | name | amount |
+----+---------+---------+
| 1 | wupeiqi | 3 |
| 2 | ale x | 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 | wupeiqi | 3 |
| 2 | ale x | 8 |
+----+---------+---------+
3 rows in set (0.00 sec)
2-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()
3 锁
在用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去申请锁,则需要配合 事务 + 特殊语法来实现。
for update
,排它锁,加锁之后,其他不可以读写。
begin;
select * from L1 where name="武沛齐" for update; -- name列不是索引(表锁)
commit;
begin; -- 或者 start transaction;
select * from L1 where id=1 for update; -- id列是索引(行锁)
commit;
基于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()
分类:
Django后端知识点
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具