V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
chaleaochexist
V2EX  ›  Python

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

  •  
  •   chaleaochexist · 194 天前 · 1530 次点击
    这是一个创建于 194 天前的主题,其中的信息可能已经有所发展或是发生改变。
    就以 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 ?
    8 条回复    2022-03-26 00:12:17 +08:00
    adoal
        1
    adoal  
       193 天前   ❤️ 1
    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
        2
    CEBBCAT  
       193 天前   ❤️ 1
    楼主对代码的阅读还是有一些偏差,比如“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
        3
    xhzhang  
       193 天前
    with 语句只是一个语法糖,自动打开一个游标,在 with 的代码块内使用完成后,会自动调用 cursor.close()关闭游标。

    user = UserModel.objects.create(name="haha") 这句是在数据库写入了一条数据,user 就代表新写入的数据,在 orm 中映射成 User 的一个类实例。只要数据库能保证生产的 id 是正确的,返回类的 id 必然就是正确的。
    chaleaochexist
        4
    chaleaochexist  
    OP
       193 天前
    @xhzhang 为什么必然是正确的? 如果 orm 是你自己写的, 你如何保证是正确的?
    这里是通过两个 sql 实现的功能。
    chaleaochexist
        5
    chaleaochexist  
    OP
       193 天前
    @CEBBCAT 我问的是 orm 的问题和你说的 MySQL LAST_INSERT_ID()无关。
    不过你说的 LAST_INSERT_ID() 这个函数, 确实是对的。 学到了。
    CEBBCAT
        6
    CEBBCAT  
       193 天前 via iPhone
    @chaleaochexist 无关……无可救药
    CEBBCAT
        7
    CEBBCAT  
       193 天前 via iPhone
    @CEBBCAT 修正一下,话可能说重了,但你一句话真的是把我憋死了。

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

    你还是多看看 ORM 代码吧,或者找一些七天 ORM 的博客看看
    chaleaochexist
        8
    chaleaochexist  
    OP
       193 天前
    @CEBBCAT
    @adoal
    谢谢 我学到了 刚才理解有误
    你们说的都对。我理解错了。
    https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-lastrowid.html
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2261 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 13:10 · PVG 21:10 · LAX 06:10 · JFK 09:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.