Mysql-锁定读(Locking Reads)

关于读锁 mysql官方文档地址:https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html

与一致性非锁定读(普通读、快照读)对应的就是锁定读,锁定读的用处很多。
首先先说下我的一些理解:
1、可以锁定数据,防止你在读取的时候,数据被更改。
2、在事务隔离级别为 REPEATABLE READ 级别情况下读取其它事务已提交的数据。这个比较有意思,本篇翻译的官方文档也没有介绍为什么,我个人的理解是,既然在事务隔离情况下锁定一个数据后其它事务不能在更改这个数据,这说明事务隔离情况下,锁也是起作用的,从这点上可以看出锁是 全局的,锁所操控的数据也是全局的而非快照数据,故而通过加锁读取数据读取到的也是全局的正式数据而非快照数据。

百度查了很多文章,感觉解释都不是很清楚或者感觉不太准确甚至明显是错误的,翻译的时候刚开始用的百度翻译和有道词典,中间还花了10块钱百度人工翻译了一下,不过后来发现了谷歌翻译,相比之下感觉百度翻译还在远古时期一样,强烈推荐谷歌翻译(没有被墙的)不要花冤枉钱~。
感觉还是官方文档看着最舒心,最能实锤,虽然这些文档都是英文的看见就觉得眼花。
翻译正文。

Locking Reads 锁定读

If you query data and then insert or update related data within the same transaction, the regular SELECT statement does not give enough protection. Other transactions can update or delete the same rows you just queried. InnoDB supports two types of locking reads that offer extra safety:

  • SELECT ... LOCK IN SHARE MODE

    Sets a shared mode lock on any rows that are read. Other sessions can read the rows, but cannot modify them until your transaction commits. If any of these rows were changed by another transaction that has not yet committed, your query waits until that transaction ends and then uses the latest values.

  • SELECT ... FOR UPDATE

    For index records the search encounters, locks the rows and any associated index entries, the same as if you issued an UPDATE statement for those rows. Other transactions are blocked from updating those rows, from doing SELECT ... LOCK IN SHARE MODE, or from reading the data in certain transaction isolation levels. Consistent reads ignore any locks set on the records that exist in the read view. (Old versions of a record cannot be locked; they are reconstructed by applying undo logs on an in-memory copy of the record.)

在一个事务中,如果你先查询数据然后再插入或更新相关数据,同时,其它的事务也可以进行update和delete你的事务中查询的行,故常规的 SELECT 语句是不能提供足够的保护的。mysql的InnoDB支持两种读锁来提供额外的安全保护:

 在某些读取的行上面设置一个共享锁。其它的事务可以读取这些行,但是不能修改它们直到你的事务提交。如果你给某些加共享锁的时候,其它的的事务正在修改这些行并且还未提交事务,那么你的查询操作将会等待知道其它的事务提交然后使用最新的数据进行查询。(PS:"使用最新的数据进行查询"这几个字要着重强调,这意味着即便数据库事务隔离级别是REPEATED READ级别,你也可以使用S锁来读取其它事务已提交的修改。为啥加了锁就能读到不加锁就不能读到呢?这个问题我也想了、找了很久,也许源码上可以找到相关的说明、实现,可实在看不下去网上贴的那些锁的源码,所以只能自己说服自己了,我的理解是:加了锁以后,这些加了锁的操作都会被放到一个锁相关的全局锁队列中,这个队列是超脱于事务之外的,所以可以读到。)

   对于查询遇到有索引的记录,SELECT ... FOR UPDATE 对这些行和相关的索引条目加锁,就像(等同于)你使用UPDATE语句来更新这些语句一样(这句话就暴露了:update语句数据库会自动加排它锁)。其它的事务想要更新这些行 或者 通过 SELECT ... LOCK IN SHARE MODE 来通过加共享锁读取这些行(获取共享锁) 或者 在某些事务隔离级别中读取这些数据,都会被阻塞。一致性读会忽略对读视图中的纪录加的任何锁(就是一致性读其实是不加锁的,即SELECT... 这种读法)。(记录的旧版本不能被锁定;它们会通过 基于这些记录内存备份的撤销日志 来重建)

These clauses are primarily useful when dealing with tree-structured or graph-structured data, either in a single table or split across multiple tables. You traverse edges or tree branches from one place to another, while reserving the right to come back and change any of these pointer values.

All locks set by LOCK IN SHARE MODE and FOR UPDATE queries are released when the transaction is committed or rolled back.

这些规则在处理树型结构数据或者图结构数据的时候是非常有用的,无论是单张表还是多张表。你遍历这些边缘和树枝从一个地方到另一个地方,在改变这些指针的值的同时保留了回滚的权利。

所有通过 SELECT ... LOCK IN SHARE MODE 和 SELECT ... FOR UPDATE 查询设置的获取的锁都会在事务提交或者会滚的时候释放。

Note 注意

Locking of rows for update using SELECT FOR UPDATE only applies when autocommit is disabled (either by beginning transaction with START TRANSACTION or by setting autocommit to 0. If autocommit is enabled, the rows matching the specification are not locked.

通过 SELECT FOR UPDATE 锁定某些行去更新,只有在autocommit被禁用的时候才有效 (可以通过使用 START TRANSACTION来开始事务 或者 设置autocommit为0来禁用autocommit。)
如果启用了autocommit,符合条件行也不会被锁定。

(疑问:为什么我测试设置autocommit为1,也被锁定了???)

Locking Read Examples 锁定读举例

Suppose that you want to insert a new row into a table child, and make sure that the child row has a parent row in table parent. Your application code can ensure referential integrity throughout this sequence of operations.

设想一下你想要在一张名为child的表中插入一条新行,同时要确保新插入的这条child行在另一张名为parent的表中有一条parent行与之对应(译者注:意思就是说要保证两张表的参照完整性,不能在你往一张表插入数据的时候另一张表中对应的记录被删除了,这样就违反了参照完整性,也会使得数据库产生脏数据。其实如果我们设置了外键就不会出现这个问题了,会抛主表找不到对应记录的异常的,但是不要这么寻根究底了,毕竟这里只是举个锁的例子。)。你的代码可以通过序列操作来保证参照完整性。

First, use a consistent read to query the table PARENT and verify that the parent row exists. Can you safely insert the child row to table CHILD? No, because some other session could delete the parent row in the moment between your SELECT and your INSERT, without you being aware of it.

首先,如果通过一致性读(就是普通读)查询PARENT表然后验证parent 行记录存在。这样可以把child行记录安全的插入CHILD表吗?答案是不安全的,因为与此同时其它的事务绘画可以在你执行SELECT和INSERT之间的一瞬间删除对应的parent行记录,而你还没有意识到。

To avoid this potential issue, perform the SELECT using LOCK IN SHARE MODE:

为了避免这个潜在的问题,可以在LOCK IN SHARE MODE(共享锁模式)下执行SELECT:

SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;

After the LOCK IN SHARE MODE query returns the parent 'Jones', you can safely add the child record to the CHILD table and commit the transaction. Any transaction that tries to acquire an exclusive lock in the applicable row in the PARENT table waits until you are finished, that is, until the data in all tables is in a consistent state.

在共享锁模式下查询并返回parent行记录'Jones'以后,你可以安全的在CHILD表中添加对应的child行,然后提交事务。任何事务试图获得在PARENT表对应的记录行的独占锁(译者注:即写入的意思,因为写入会自动获取排它锁)都会等待直到你的共享锁模式读所在的事务结束为止,也就是说,直到所有表中的数据处于一致状态(这后半句没看懂)。

For another example, consider an integer counter field in a table CHILD_CODES, used to assign a unique identifier to each child added to table CHILD. Do not use either consistent read or a shared mode read to read the present value of the counter, because two users of the database could see the same value for the counter, and a duplicate-key error occurs if two transactions attempt to add rows with the same identifier to the CHILD table.

举另外一个例子,CHILD_CODES表中有一个整型字段,用来给CHILD表中每一个child记录分配一个唯一id。如果不使用一致性读或者共享锁读 去读取计数器当前值,由于有可能多个用户同时从计数器获取到同一个值,然后多个事务同时添加行到CHILD表中,这时候就会报key冲突错误。

Here, LOCK IN SHARE MODE is not a good solution because if two users read the counter at the same time, at least one of them ends up in deadlock when it attempts to update the counter.

这种情况,共享锁模式是不太适用的,因为如果两个用户同时读计数器,然后同时去update计数器的时候,至少有一个会报死锁错误。

To implement reading and incrementing the counter, first perform a locking read of the counter using FOR UPDATE, and then increment the counter. For example:

为了实现读取然后递增计数器,首先需要使用FOR UPDATE 排它锁锁定读计数器,然后再递增计数器。如下:

SELECT counter_field FROM child_codes FOR UPDATE;UPDATE child_codes SET counter_field = counter_field + 1;

SELECT ... FOR UPDATE reads the latest available data, setting exclusive locks on each row it reads. Thus, it sets the same locks a searched SQL UPDATE would set on the rows.

使用SELECT ... FOR UPDATE读取最新的有效数据,在读取的行上面设置排它锁。UPDATE也会设置相同的锁在这些行上。

The preceding description is merely an example of how SELECT ... FOR UPDATE works. In MySQL, the specific task of generating a unique identifier actually can be accomplished using only a single access to the table:

前面的描述仅仅是 SELECT ... FOR UPDATE 是怎样工作的一个例子。其实在MySQL中,生成唯一标识符只需要对表进行一次访问就可以了:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();

The SELECT statement merely retrieves the identifier information (specific to the current connection). It does not access any table.

SELECT语句仅仅检索标识符信息(针对当前链接)。它并访问任何表。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值