数据库锁

select for update

由于InnoDB 预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。

例1: (明确指定主键,并且有此数据,row lock)

1
SELECT * FROM products WHERE id='3' FOR UPDATE;

例2: (明确指定主键,若查无此数据,无lock)

1
SELECT * FROM products WHERE id='-1' FOR UPDATE;

例3: (无主键,table lock)

1
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;

例4: (主键不明确,table lock)

1
SELECT * FROM products WHERE id<>'3' FOR UPDATE;

例5: (主键不明确,table lock)

1
SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;

注1: FOR UPDATE 仅适用于InnoDB,且必须在事务区块(BEGIN/COMMIT)中才能生效。

【几个问题】

  1. 什么时候解锁?
    • 需要commit之后才会解锁
  2. 如果在for update一个行后网络断掉,这个行会在什么时候解锁?
    • 服务器无法判断连接是否断开,一般需要手工解锁;如果主动关闭连接,服务器会解锁
  3. 如何查询一个行是否被锁定?
    • select …. from update nowait,不需要等待,如果已被锁,立即失败,如果未被锁,立即成功锁定

READ COMMITTED 与 REPEATABLE READ

先看一个例子

t1 t2
begin;
begin;
update mytest set t3=’cc’ where t2=’bb’;
select * from mytest where t2=’bb’; Mark A
commit;
select * from mytest where t2=’bb’; Mark B
commit;
  1. 上面Mark A处显然t1已经给记录加了X锁,并且在事务内修改了数据,此时t2看到的数据是什么?
  2. 上面Mark B处事务t1已经提交此时t2看到的数据是什么?

每个行记录有多个版本。行多版本通过undo日志实现,undo日志包括了所有用来恢复历史版本数据的信息,那么我们只要将“不同版本”指针指向不同时间节点的undo日志,这样读取的时候通过对不同时间节点的undo日志进行恢复,得到不同的版本数据。同时对于undo日志的读取是不需要加锁的,因此这极大地提高了数据库的并发性。

t2此时看到的应该是历史版本的数据,也就是t1修改之前的数据

READ COMMITTED值的是一个事务可以读取其他事务已经提交的数据,而REPEATABLE READ要求一个事务在事务内可以重复读取一条记录。

innodb默认的隔离级别为REPEATABLE READ,t2在Mark B的地方看到的应该是老数据

如果此时的事务隔离级别为READ COMMITTED,t2在Mark B处看到的应该是新数据