GVKun编程网logo

InnoDB SELECT … FOR UPDATE 语句锁定表中的所有行(for update锁表后select会发生什么)

11

本文将分享InnoDBSELECT…FORUPDATE语句锁定表中的所有行的详细内容,并且还将对forupdate锁表后select会发生什么进行详尽解释,此外,我们还将为大家带来关于InnoDB中的

本文将分享InnoDB SELECT … FOR UPDATE 语句锁定表中的所有行的详细内容,并且还将对for update锁表后select会发生什么进行详尽解释,此外,我们还将为大家带来关于InnoDB中的select .. for update语句、java – JDBC使用SELECT FOR UPDATE锁定一行,不起作用、join update vs sub-selects update、lock in share mode 和 select for update的相关知识,希望对你有所帮助。

本文目录一览:

InnoDB SELECT … FOR UPDATE 语句锁定表中的所有行(for update锁表后select会发生什么)

InnoDB SELECT … FOR UPDATE 语句锁定表中的所有行(for update锁表后select会发生什么)

MySQL 服务器版本 5.1.41,启用了 InnoDB 插件。我有以下三个发票表:invoices、invoice_components 和 invoice_expenses。表发票有 invoice_id 主键。invoice_components 和 invoice_expenses 都链接到表发票,其中 invoice_id 作为非唯一的外键(每张发票可以有多个组件和多个费用)。两个表都有这个外键的 BTREE 索引。

我有以下交易:

交易 1

START TRANSACTION;
SELECT * FROM invoices WHERE invoice_id = 18 FOR UPDATE;
SELECT * FROM invoice_components WHERE invoice = 18 FOR UPDATE;
SELECT * FROM invoice_expenses WHERE invoice = 18 FOR UPDATE;
第一个事务一切正常,行被选中并锁定。

交易2

START TRANSACTION; SELECT * FROM invoices WHERE invoice_id = 19 FOR UPDATE; SELECT * FROM invoice_components WHERE invoice = 19 FOR UPDATE; SELECT * FROM invoice_expenses WHERE invoice = 19 FOR UPDATE; 

第二个事务ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction为第三个查询返回。

当我尝试选择… FOR UPDATE其他发票及其组成和费用时,也会发生同样的情况。似乎第一笔交易已经锁定了 invoice_expenses 表中的所有行。任何想法为什么会发生这种情况?

附加信息

事务 2 在事务 1 的第三次查询之后开始。服务器上没有其他用户、连接或事务。

该问题发生在默认的 REPEATABLE READ 事务隔离级别。它通过更改为 READ COMMITTED 级别来修复。这是一个解决方案,但它仍然没有解释为什么问题发生在 invoice_expenses 而不是 invoice_components 上。

答案1

小编典典

我怀疑这与间隙锁和next-key 锁以及REPEATABLE READ行为的差异有关:

摘录来自 MySQL 文档:SET TRANSACTION 语法

对于锁定读取(SELECT with FOR UPDATE 或 LOCK IN SHARE MODE)、UPDATE 和 DELETE 语句,锁定取决于语句是使用具有唯一搜索条件的唯一索引还是范围类型搜索条件。对于具有唯一搜索条件的唯一索引,InnoDB 只锁定找到的索引记录,而不锁定它之前的间隙。对于其他搜索条件,InnoDB 锁定扫描的索引范围,使用间隙锁或 next-key(间隙加索引记录)锁来阻止其他会话插入范围所覆盖的间隙。

和读取提交:

注意:在 MySQL 5.1 中,如果使用 READ COMMITTED 隔离级别或启用了 innodb_locks_unsafe_for_binlog 系统变量,则除了外键约束检查和重复键检查之外,没有 InnoDB 间隙锁定。此外,在 MySQL 评估 WHERE 条件后,将释放不匹配行的记录锁。

也许 OP 可以告诉我们innodb_locks_unsafe_for_binlog system变量的状态,以及在更改此变量的设置时是否发生相同的锁定。

此外,如果相同的锁定发生在非顺序 id 的情况下,例如18and 20,或18and99

InnoDB中的select .. for update语句

InnoDB中的select .. for update语句

InnoDB中的select .. for update语句:

1)select .. for update语句仅适用于InnoDB
2)select .. for update语句必须在事务中才能生效。
3)在执行事务中的select .. for update语句时,MySQL会对查询结果集中的每行数据都添加排他锁(行锁、表锁),其它线程对锁定行的 更新、删除、select .. for update查询 这3种操作都会被阻塞,一般的select语句不会被阻塞。
4)查看自动提交是否开启(1表示开启,0表示关闭,默认开启): select @@autocommit
5)InnoDB行级锁的实现:InnoDB的行级锁是通过在索引上加锁来实现的,所以只有通过明确的索引来查找数据时才会使用行级锁。

排它锁的选择:

若where条件中明确指定了主键,且该行数据存在,则只锁定该行,故排它锁为行锁(row lock)。
若where条件中明确指定了主键,但是该行数据不存在,则不会加锁。

若where条件中明确指定了索引,且该行数据存在,则只锁定该行,故排它锁为行锁(row lock)。
若where条件中明确指定了索引,但是该行数据不存在,则不会加锁。

若where条件中未明确指定主键或索引,则会锁定全表,故排它锁为表锁(table lock)。
注:未明确指定 即 未指定(主键/索引) 或 指定的是(主键/索引)的范围

eg: # 只锁定message_id为1的行 set autocommit=0; begin; select * from t_message where message_id=1 for update; # message_id为主键 commit;

# 锁定全表
set autocommit=0;
begin;
select * from t_message where message_id>1 for update; # message_id为主键
commit;

# 锁定全表
set autocommit=0;
begin;
select * from t_message where type=''good'' for update; # good非索引列
commit;


其它线程因为等待(排它锁)超时而报错:
update t_message set title=''asdf'' where message_id=1;
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction

java – JDBC使用SELECT FOR UPDATE锁定一行,不起作用

java – JDBC使用SELECT FOR UPDATE锁定一行,不起作用

我遇到 MySQL的SELECT问题.. FOR UPDATE,这是我试图运行的查询:
SELECT * FROM tableName WHERE HostName='UnkNownHost' 
        ORDER BY UpdateTimestamp asc limit 1 FOR UPDATE

在此之后,相关的线程将执行UPDATE并更改HostName,然后它应该解锁该行.

我正在运行一个多线程的java应用程序,所以3个线程正在运行这个sql语句,但是当线程1运行它时,它不会将其结果从线程2&因此,线程2& 3获得相同的结果,他们可以更新同一行.

每个线程也都有自己的MysqL连接.

我正在使用Innodb,事务隔离= READ-COMMITTED,并且在执行select for update之前自动提交已关闭

我可能会错过什么吗?或许还有更好的解决方案?
非常感谢.

代码:

public BasicJDBCDemo()
{
    Le_Thread newThread1=new Le_Thread();
    Le_Thread newThread2=new Le_Thread();
    newThread1.start();
    newThread2.start();         
}

线程:

class Le_Thread extends Thread  
{

    public void run() 
    {
    tring name = Thread.currentThread().getName();
        System.out.println( name+": Debut.");
    long oid=Util.doSelectLockTest(name);
    Util.doUpdateTest(oid,name);        
    }

}

选择 :

public  static long doSelectLockTest(String threadName)
  {
    System.out.println("[OUTPUT FROM SELECT Lock ]...threadName="+threadName);
    PreparedStatement pst = null;
    ResultSet rs=null;
    Connection conn=null;
    long oid=0;
    try
    {
     String query = "SELECT * FROM table WHERE Host=? 
                               ORDER BY Timestamp asc limit 1 FOR UPDATE";


      conn=getNewConnection();
      pst = conn.prepareStatement(query);
      pst.setString(1,DbProperties.UnkNownHost);
      System.out.println("pst="+threadName+"__"+pst);
      rs = pst.executeQuery();

      if (rs.first())
      {
        String s = rs.getString("HostName");
        oid = rs.getLong("OID");
        System.out.println("oid_oldest/host/threadName=="+oid+"/"+s+"/"+threadName);

      }   

    }
    catch (sqlException ex)
    {
      ex.printstacktrace();
    }
    finally
    {
        DBUtil.close(pst);
        DBUtil.close(rs);
        DBUtil.close(conn);
    }
    return oid;
  }

请帮忙…. :

结果:

Thread-1: Debut.
Thread-2: Debut.
[OUTPUT FROM SELECT Lock ]...threadName=Thread-1
New connection..
[OUTPUT FROM SELECT Lock ]...threadName=Thread-2
New connection..
pst=Thread-2: SELECT * FROM b2biCheckPoint  WHERE HostName='UnkNownHost' ORDER BY UpdateTimestamp asc limit 1 FOR UPDATE
pst=Thread-1: SELECT * FROM b2biCheckPoint  WHERE HostName='UnkNownHost' ORDER BY UpdateTimestamp asc limit 1 FOR UPDATE
oid_oldest/host/threadName==1/UnkNownHost/Thread-2
oid_oldest/host/threadName==1/UnkNownHost/Thread-1
[Performing UPDATE] ... oid = 1,thread=Thread-2
New connection..
[Performing UPDATE] ... oid = 1,thread=Thread-1
pst_threadname=Thread-2: UPDATE b2bicheckpoint SET HostName='1_host_Thread-2',UpdateTimestamp=1294940161838 where OID = 1
New connection..
pst_threadname=Thread-1: UPDATE b2bicheckpoint SET HostName='1_host_Thread-1',UpdateTimestamp=1294940161853 where OID = 1

解决方法

你非常困惑,但至少在编辑后事情看起来更好.有多种方法可以做到这一点,但我发现最好的方法是实际使用JDBC的ResultSet.update *方法:

首先,您需要使用ResultSet.CONCUR_UPDATABLE参数准备SELECT … FOR UPDATE语句,如下所示:

ps = conn.prepareStatement(query,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_UPDATABLE);

然后,您必须使用ResultSet实际更新表:

if(rs.next())
{
    rs.updateString(columnIndex,"new_hostname");
    rs.updateRow();
}

第三,您可能需要使用我可以在更新中看到的事务.希望你的DbUtil.close方法不会抛出任何异常,检查null等等.另外,如果你的方法变得更复杂,你也应该有回滚逻辑.

你不应该因为任何原因修改my.ini.

join update vs sub-selects update

join update vs sub-selects update

开发说使用多表关联进行更新的时候发现没有正确的更新记录(事后发现是条件问题),之前一直没怎么使用关联更新,这次看了下,发现了个问题,当返回多行记录的时候并不会像传统的子查询更新那样报错,而是随机选择一个记录进行更新( 貌似最后一个? ) 因此

开发说使用多表关联进行更新的时候发现没有正确的更新记录(事后发现是条件问题),之前一直没怎么使用关联更新,这次看了下,发现了个问题,当返回多行记录的时候并不会像传统的子查询更新那样报错,而是随机选择一个记录进行更新(貌似最后一个?
因此,虽然关联更新会快很多,但是要考虑的这个不确定带来的隐患。

gtlions=# create table joinupdate1(id int,name varchar(20));
NOTICE:  Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'id' as the Greenplum Database data distribution key for this table.
HINT:  The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE TABLE
gtlions=# create table joinupdate2(id int,name varchar(20));
NOTICE:  Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'id' as the Greenplum Database data distribution key for this table.
HINT:  The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew.
CREATE TABLE
gtlions=# insert into  joinupdate1 values(1,'a');
INSERT 0 1
gtlions=# insert into  joinupdate1 values(2,'b');
INSERT 0 1
gtlions=# insert into  joinupdate2 values(1,'b');
INSERT 0 1
gtlions=# insert into  joinupdate2 values(2,'b');
INSERT 0 1
gtlions=# truncate table joinupdate2;
TRUNCATE TABLE
gtlions=# insert into  joinupdate2 values(1,'c');
INSERT 0 1
gtlions=# insert into  joinupdate2 values(2,'d'); 
INSERT 0 1
gtlions=# select * from joinupdate1;
 id | name 
----+------
  1 | a
  2 | b
(2 rows)

gtlions=# select * from joinupdate2;
 id | name 
----+------
  1 | c
  2 | d
(2 rows)

gtlions=# begin;
BEGIN
gtlions=# update joinupdate1 set name=(select name from joinupdate2 where joinupdate1.id=joinupdate2.id);
UPDATE 2
gtlions=# select * from joinupdate1;
 id | name 
----+------
  1 | c
  2 | d
(2 rows)

gtlions=# rollback;
ROLLBACK
gtlions=# end;
WARNING:  there is no transaction in progress
COMMIT
gtlions=# select * from joinupdate1;
 id | name 
----+------
  1 | a
  2 | b
(2 rows)

gtlions=# begin;
BEGIN
gtlions=# update joinupdate1 set name=joinupdate2.name from joinupdate2 where joinupdate1.id=joinupdate2.id;
UPDATE 2
gtlions=# select * from joinupdate1;
 id | name 
----+------
  1 | c
  2 | d
(2 rows)

gtlions=# rollback;
ROLLBACK
gtlions=# insert into joinupdate2 values(1,'e');
INSERT 0 1
gtlions=# begin;
BEGIN
gtlions=# update joinupdate1 set name=(select name from joinupdate2 where joinupdate1.id=joinupdate2.id);
ERROR:  more than one row returned by a subquery used as an expression  (seg0 slice2 h1:40000 pid=14123)
gtlions=# rollback;
ROLLBACK
gtlions=# begin;
BEGIN
gtlions=# update joinupdate1 set name=joinupdate2.name from joinupdate2 where joinupdate1.id=joinupdate2.id;
UPDATE 2
gtlions=# select * from joinupdate1;
 id | name 
----+------
  1 | e
  2 | d
(2 rows)

gtlions=# update joinupdate1 set name=joinupdate2.name from joinupdate2 where joinupdate1.id=joinupdate2.id;
UPDATE 2
gtlions=# select * from joinupdate1;
 id | name 
----+------
  1 | e
  2 | d
(2 rows)

gtlions=# update joinupdate1 set name=joinupdate2.name from joinupdate2 where joinupdate1.id=joinupdate2.id;
UPDATE 2
gtlions=# select * from joinupdate1;
 id | name 
----+------
  1 | e
  2 | d
(2 rows)

gtlions=# insert into joinupdate2 values(1,'f');
INSERT 0 1
gtlions=# update joinupdate1 set name=joinupdate2.name from joinupdate2 where joinupdate1.id=joinupdate2.id;
UPDATE 2
gtlions=# select * from joinupdate1;
 id | name 
----+------
  1 | f
  2 | d
(2 rows)

gtlions=# rollback;
ROLLBACK
登录后复制

-eof-

lock in share mode 和 select for update

lock in share mode 和 select for update

lock in share mode 和 select for update

2018年07月11日 01:57:58 道不虚行只在人 阅读数 146
 
 版权声明:欢迎转载,请注明出处 https://blog.csdn.net/xinzhongtianxia/article/details/80994259

同步发表于:http://blog.lanjingdejia.com/articles/2018/07/11/1531245873812.html
许多同学或应该都听过"select … lock in share mode" 和 “select … for update”,但是或多或少会有以下几个疑问:

  1. 这俩货是什么含义?
  2. 和普通的select有什么区别呢?
  3. 什么场景下用?用哪个?

含义

我们直接从mysql官网上看它们的定义:

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.

翻译一下:在你正在读的行设置一个共享锁,其他session也可以读这些行,但是直到你读完这些行,事务提交释放锁之后,其他sesstion才能更改这些行。如果你要读取的行正在被其他session修改,那么读取会卡住,直到其他session修改完毕,读取才能继续,并且读到的是最新版本的数据。

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.

翻译一下:对于select的数据,会对这些行及其相关行加锁,效果和用update更新这些行时是一样的。其他对这些数据进行操作,无论是update也好,上面提到的SELECT … LOCK IN SHARE MODE也好,甚至在某些事务隔离级别下读取这些数据也好,所有这些操作都会被阻塞。

通俗一点

lock in share mode能保证读到的一定是最新的值,而且保证读的过程中不会被其他session修改。因为加的是共享锁,所以多个session同时lock in share mode时都可以读到数据。
select for update更严格一些,加的是排它锁(根据情况,有时还会加间隙锁),会阻塞住所有其他session对该数据的操作。

和普通的select的区别

mysql(InnoDB)的select有三种:

  • 啥也不加的普通的select
  • select … lock in share mode
  • select … for update

上面提到的共享锁、排它锁等等,这些锁对普通的select是没有用的,普通的select是永远不会被阻塞的。下面举例说明。

普通select和lock in share mode的区别

  1. session A正在修改(update或者delete)某一行数据,如果这时session B进行普通的select,session B是不会被阻塞的,且读到的是session A修改之前的数据。如果这时session C进行select … lock in share mode,那么session C将会阻塞,直到session A所在的事务提交(或者回滚释)放锁之后,session C才会继续进行,且读到的使session A修改过后的数据。
  2. session A 正在进行普通的select,如果这时session B 进行update操作,那么session B不会被阻塞。但是如果session A进行的是select … lock in share mode,那么session B的update操作会被阻塞,直到session A提交之后,session B才能继续进行。

普通select和select for update的区别

区别同上

lock in share mode和select for update的区别

  1. session A正在 lock in share mode, 此时session B 进行lock in share mode 操作不会被阻塞,但是如果此时session C进行select for update的话,就会被阻塞。
  2. 如果session A 正在select for update,如果此时session B 进行lock in share mode, 那么session B会被阻塞,如果此时session C也进行select for update操作,那么session C也会被阻塞。

使用场景

select … lock in share mode的使用场景

引用mysql官网的例子,有两张表:parent表和child表,向child表插入数据时,要保证child的parent在parent表中存在,删除parent表时,会将parent以及该parent的child都删掉。一般的操作是先select,得到parentId,然后向child中插入一条数据(关联parentId),但是这样是有问题的,如果select之后,插入之前,另一个session将parent删掉了,那么向child表中插入的数据并不会受到影响,最终造成该child没有parent的状况。
根据上面提到的特性,select时用select … lock in share mode就能解决这个问题。其实select … lock in share mode 在平时用到的场景很少很少,用的比较多的还是select … for update。

select … for update的使用场景

账户表中有一个字段money,取出来后,将money更新(比如加上一个值)后再存进去。对于这个场景,如果两个session同时select,比如都取出来的是100,然后都加30, 都变成130,然后都update money,最终money的值是130,与预期的160不符。这个时候用select … for update 就能完美解决这个问题,这时因为两个session不能同时select … for update。

思考

上述select … for update的场景如果使用select … lock in share mode会怎样?
答案是,会很大概率造成死锁,造成死锁的原因是:session A和session B同时select lock in share mode, 这时都未提交事务,session A 继续执行update操作,此时因为session B事务还没提交,锁还没释放,所以session A的update操作会被阻塞,等待session B释放锁,同样的,session B此时也在等待session A提交事务释放锁,这就发生了死锁。
所以lock in share mode 使用时要仔细检查,确认该场景是否真的适用且不会发生死锁。

关于InnoDB SELECT … FOR UPDATE 语句锁定表中的所有行for update锁表后select会发生什么的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于InnoDB中的select .. for update语句、java – JDBC使用SELECT FOR UPDATE锁定一行,不起作用、join update vs sub-selects update、lock in share mode 和 select for update等相关内容,可以在本站寻找。

本文标签: