django orm/ 其他 orm 中 create 一条数据,返回的 model id 为什么一定是正确的?

2022-03-25 20:22:27 +08:00
 chaleaochexist
就以 django orm 举例子

user = UserModel.objects.create(name="haha")

print(user.id)
>>> 666


我的问题是 这个 user.id 为什么能保证是正确的。 当有并发插入的时候,django/数据库是如何保证事物约束的?

我翻了 django 的源码,django 是利用两条语句实现的
https://github.com/django/django/blob/main/django/db/models/sql/compiler.py

1. insert # cursor.execute(sql, params)
2. select id from user # self.connection.ops.last_insert_id(

两条 sql 语句中间如何插入了另一条数据, 获取最新的 id 就是不正确的了。

不知道是不是这句话起作用了:with self.connection.cursor() as cursor:


另一个问题也困扰我好多年: 什么是 cursor ?
1900 次点击
所在节点    Python
8 条回复
adoal
2022-03-25 21:35:34 +08:00
https://en.wikipedia.org/wiki/Insert_(SQL)#Retrieving_the_key

用子句方式的自然不存在这个问题。用存储过程方式的,是通过数据库内部的实现保存了当前会话里最近一次的数据变更操作结果,而不是再写一条 SQL 到表里去查。注意,你所提到的“self.connection.ops.last_insert_id”实际上是在一个 else:分支里,前面还有 if 和 elif 两个分支,处理的就是用子句方式的情况。

所以其实答案的关键不是 Django 怎么做,而是底层的 SQL 怎么做。这个功能的正确性是 RDBMS 本身保证的。如果某个 RDBMS 不能保证,那 Django 怎么写也没用。
CEBBCAT
2022-03-25 21:39:56 +08:00
楼主对代码的阅读还是有一些偏差,比如“self.connection.ops.last_insert_id”在做的应该不是“select id from user”,我头说起吧,假定你使用的是 MySQL 。

MySQL 提供了 LAST_INSERT_ID() [0]函数,调用它将会返回当前回话上次插入的第一个生成的值,一般就是 AUTO_INCREMENT 。所以 Django 是有能力获取正确的用户 ID 的。而这个函数的运作方式从文档也可以看到,是不受其他 client 影响的,所以不用担心并发。


本来还打了一大堆 InnoDB 、X 锁之类的,重新审题发现好像是我想多了,放在参考资料吧,你再用里面提到的关键词搜索,应该能搜到不少资料。

忘了说,你可以看看 “self.connection.ops.last_insert_id” ,再向下找应该就可以找到 LAST_INSERT_ID() 了。另外虽然我好久没写 Python 了,但 with 显然不是做这个用的。再另外,想问一下楼主是怎么学习 Python 的?感觉基础还需要再扎实一点呀。再另外,我觉得可以把 cursor 理解为一个会话,当然了,最好找专门的文章。


0. https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_last-insert-id
参考资料:
https://segmentfault.com/a/1190000023869573
https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-insert-intention-locks
https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html

https://github.com/django/django/blob/379bb201ed346a76e322fe7c6cbd13cb4a6e68a1/django/db/models/sql/compiler.py#L1662-L1666
xhzhang
2022-03-25 22:08:34 +08:00
with 语句只是一个语法糖,自动打开一个游标,在 with 的代码块内使用完成后,会自动调用 cursor.close()关闭游标。

user = UserModel.objects.create(name="haha") 这句是在数据库写入了一条数据,user 就代表新写入的数据,在 orm 中映射成 User 的一个类实例。只要数据库能保证生产的 id 是正确的,返回类的 id 必然就是正确的。
chaleaochexist
2022-03-25 23:49:44 +08:00
@xhzhang 为什么必然是正确的? 如果 orm 是你自己写的, 你如何保证是正确的?
这里是通过两个 sql 实现的功能。
chaleaochexist
2022-03-25 23:58:42 +08:00
@CEBBCAT 我问的是 orm 的问题和你说的 MySQL LAST_INSERT_ID()无关。
不过你说的 LAST_INSERT_ID() 这个函数, 确实是对的。 学到了。
CEBBCAT
2022-03-26 00:00:12 +08:00
@chaleaochexist 无关……无可救药
CEBBCAT
2022-03-26 00:10:18 +08:00
@CEBBCAT 修正一下,话可能说重了,但你一句话真的是把我憋死了。

ORM 没什么了不起的,无非就是代码多一点的 SQL 生成器,楼上说得很有道理,DBMS 不支持的,ORM 写出花来也没用。

你还是多看看 ORM 代码吧,或者找一些七天 ORM 的博客看看
chaleaochexist
2022-03-26 00:12:17 +08:00
@CEBBCAT
@adoal
谢谢 我学到了 刚才理解有误
你们说的都对。我理解错了。
https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-lastrowid.html

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/842931

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX