V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
stach
V2EX  ›  数据库

oracle 幻读, 不可重复读是如何避免的?

  •  
  •   stach · 2021-09-06 15:52:52 +08:00 · 1599 次点击
    这是一个创建于 1172 天前的主题,其中的信息可能已经有所发展或是发生改变。

    lz 经常在 v 站评论帮助他人, 很少发帖, 这次借贵宝地, 请教路过的各位大佬谈谈看法, 解答一下疑惑:

    oracle 在具体实践中是否会考虑 "不可重复读", "幻读" 问题, 又是如何去解决的呢?


    下面是楼主做的准备工作:

    • 事务的隔离级别

      • MySQL(InnoDB)
        • 默认的事务隔离级别是 REPEATABLE-READ(可重复读)
      • oracle
        • 默认的事务隔离级别是 READ COMMITTED(读提交)
    • 存在的问题

      • MySQL 存在 "幻读" 问题
      • oracle 存在 "不可重复读" "幻读" 问题
    • 解决方法

      • MySQL 使用 Next-Key Lock(锁定一个范围,并锁定记录本身), 具体表现就是在事务内查询时 select... for update
      • oracle 暂时没看到具体实践的资料

    楼主查过 oracle 官网的文档, 似乎也没有具体的答案, db2 的资料多一些, 直接修改隔离级别为 "序列化读" 也不现实.

    6 条回复    2021-09-11 01:21:29 +08:00
    sun1991
        1
    sun1991  
       2021-09-06 16:39:00 +08:00
    给个具体点的例子吧?
    stach
        2
    stach  
    OP
       2021-09-06 17:01:17 +08:00
    @sun1991

    这里有个 MySQL 的例子: https://segmentfault.com/a/1190000016566788, 我这里是没有 oracle 的.
    lz 是互联网从业者, 几乎都使用 MySQL 数据库, 所以疑惑 oracle 从业者的实践姿势
    stach
        3
    stach  
    OP
       2021-09-06 19:27:21 +08:00
    可能 v 站大多是用 MySQL 的兄弟吧, 这个问题石沉大海了 :(
    sun1991
        4
    sun1991  
       2021-09-07 09:42:32 +08:00   ❤️ 1
    以下是我的个人理解:

    Oracle 一共只有两种隔离级别 (抛开 readonly 不谈). Serialize 一般不用. 常用的也就是 read committed.

    Oracle 的锁是加在物理 row 上面, 也就是说, 如果 row 不存在, 那么就不能加锁. 所以 Oracle 没有 MySQL 那么多的弯弯绕. 如果你要应对不可重复读, 幻读问题, 那么老老实实地用 serialize 隔离级别.

    Oracle 的 serialize 隔离级别其实是一种乐观锁, 它限制的是自己的 session 而非其它 session 的写操作, 意思是当检测到自己 session 违反了 serialize 隔离级别的限制, 就报错.

    总之, Oracle 的优化目标是尽可能使得所有的 read 操作尽快完成, 所以能不锁的一概不锁.
    stach
        5
    stach  
    OP
       2021-09-07 10:33:38 +08:00
    @sun1991
    赞同老哥的说法, 不能用 MySQL 的理论去套 oracle, 开启事务的时候设定隔离级别为 serialize 不失为解决 不可重复读, 幻读的好方法.

    其实 MySQL 官方的 Python Connector 库开启事务的时候, 就有指定隔离级别的参数, 只是我们几乎都习惯了固定为 REPEATABLE READ.

    ```python
    def start_transaction(self, consistent_snapshot=False,
    isolation_level=None, readonly=None):
    """Start a transaction

    This method explicitly starts a transaction sending the
    START TRANSACTION statement to the MySQL server. You can optionally
    set whether there should be a consistent snapshot, which
    isolation level you need or which access mode i.e. READ ONLY or
    READ WRITE.

    For example, to start a transaction with isolation level SERIALIZABLE,
    you would do the following:
    >>> cnx = mysql.connector.connect(..)
    >>> cnx.start_transaction(isolation_level='SERIALIZABLE')

    Raises ProgrammingError when a transaction is already in progress
    and when ValueError when isolation_level specifies an Unknown
    level.
    """
    ```
    Hozzz
        6
    Hozzz  
       2021-09-11 01:21:29 +08:00   ❤️ 1
    select for update
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1617 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 16:55 · PVG 00:55 · LAX 08:55 · JFK 11:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.