文档章节

[用事实说明两个凡是]一个由mysql事务隔离级别造成的问题分析

周翼翼
 周翼翼
发布于 2015/11/24 12:52
字数 2050
阅读 3693
收藏 109
点赞 4
评论 44

背景

最近要做一个批跑服务, 基本逻辑就是定时扫描数据库的记录, 有满足条件的就进行处理(一条记录代表一个任务,以下任务与记录含义相同). 要求支持多机部署批跑服务.

批跑支持多机部署实现方案

要实现多机部署, 只要保证每个批跑服务实例每次只获取一条记录, 处理完再获取下一条即可. 其中最种要的是避免不同的实例获取到同一条记录,即所谓抢任务.

先看表结构设计:

create database if not exists ae;
create table ae.task (
id int primary key,
status int);
-- status为0说明任务可处理,其它不可处理

以上是简化的表结构,但足以说明本文试图说明的问题.

要避免抢任务, oracle的做法, 直接

update ae.task set status=1 where status=0 and rownum = 1 returning id

即可.

mysql的要啰嗦一点:

select id from ae.task where status=0; -- 得到ID
update ae.task where id = ${id} and status=0;

这两个sql,第一个sql用于获取符合条件的任务, 第二个sql用户将任务锁定. 在并发的场景下, 有可能不同的批跑实例的第一个SQL会返回相同的记录, 但第二个sql只有一个会更新成功, 通过判断affected rows即可知道哪个锁定成功. 锁定成功的继续处理本任务, 锁定失败的继续处理其它任务.

问题现象

管理后台提交了一个任务后, 两个批跑实例恰好同时启动, 进入抢任务环节. 结果发现异常, 其中一个实例成功抢到任务, 但另一个实例则挂死了:

抢到任务的实例:

2015-11-23 19:42:01|INFO|exec_task.php|40||get one task: 11
...
2015-11-23 19:42:01|INFO|exec_task.php|107||line_count: 9
2015-11-23 19:42:01|INFO|exec_task.php|147||fork child success: 8346
2015-11-23 19:42:01|INFO|exec_task.php|264||[0] pid: 8346, start: 0, stop: 0

2015-11-23 19:42:01|INFO|exec_task.php|147||fork child success: 8347
2015-11-23 19:42:01|INFO|exec_task.php|264||[1] pid: 8347, start: 1, stop: 1

2015-11-23 19:42:01|INFO|exec_task.php|147||fork child success: 8348
2015-11-23 19:42:01|INFO|exec_task.php|264||[2] pid: 8348, start: 2, stop: 2

2015-11-23 19:42:01|INFO|exec_task.php|147||fork child success: 8349
...

没有抢到任务的实例:

2015-11-23 19:42:01|INFO|function.inc.php|100||task_id 11 is locked by another process, get next task
2015-11-23 19:42:01|INFO|function.inc.php|100||task_id 11 is locked by another process, get next task
2015-11-23 19:42:01|INFO|function.inc.php|100||task_id 11 is locked by another process, get next task
2015-11-23 19:42:01|INFO|function.inc.php|100||task_id 11 is locked by another process, get next task
2015-11-23 19:42:01|INFO|function.inc.php|100||task_id 11 is locked by another process, get next task
2015-11-23 19:42:01|INFO|function.inc.php|100||task_id 11 is locked by another process, get next task
2015-11-23 19:42:01|INFO|function.inc.php|100||task_id 11 is locked by another process, get next task
2015-11-23 19:42:01|INFO|function.inc.php|100||task_id 11 is locked by another process, get next task
2015-11-23 19:42:01|INFO|function.inc.php|100||task_id 11 is locked by another process, get next task

可以看到没有抢到任务的实例进入了死循环.

原因分析

按照我们之前的设计, 如果第二条SQL锁定任务的时候失败了, 获取下一个任务. 应当不会死循环. 死循环的原因是因为没有抢到任务的实例, 在执行第一个SQL的时候, 一直返回了相同的记录(id=11,实际上当时也只有一条记录)

请注意, 抢到任务的实例抢到任务后, 会把状态更新并提交, 按说抢不到任务的实例会看到此状态更新,并导致第一条sql查不到数据,然后 正常退出.

而事实上抢不到任务的实例看不到此变化, 说明事务隔离级别(Transaction Isolation Level)不是"READ COMMITED", 而是其它. 经确认, 级别是"REPEATABLE-READ"

mysql> select @@TX_ISOLATION;
+-----------------+
| @@TX_ISOLATION  |
+-----------------+
| REPEATABLE-READ |

"REPEATABLE-READ" 看到的数据是事务启动时的样子,所以看不到抢到任务的实例对任务状态的修改. 进而导致死循环.

请注意执行第一个SQL查询满足条件的任务是在一个事务内进行的. 此事务实际上是业务的需要, 除了获取到任务,还需要获取其它资源,如果获取不到其它 资源, 则rollback任务,以便下次处理.

ORACLE相应的事务隔离级别是"Serializable Isolation Level", 如上描述的这个场景, 在ORACLE下的反应是抢不到任务的实例在试图更新任务状态的 时候,会返回一个"ORA-08177: Cannot serialize access for this transaction"错误, 程序也可以正常退出. 详见<<Oracle Database Concepts 11g Release 2 (11.2) E40540-04>> 第9章"Overview of Oracle Database Transaction Isolation Levels"

mysql在"REPEATABLE-READ"的事务隔离级别上的表现是不能让人满意的. 查询到的数据是事务启动时的样子,但更新的时候看到的数据又是其它事务提交 后的结果,并且update也没有错误提示.

而"SERIALIZABLE"更糟糕, 如果同时开了两个session, 干脆直接锁表了, 谁了更新不了. 这就势必造成另一个问题, 既然大家都更新不了,那就rollback事务, 重试呗. 但是重试也是很有可能大家再同时开了事务,又锁死了, 一直死循环. 为了解决这种情况,可能的做法是, 各自等待一个随机时间再重试,让随机打破这个僵局. 不知道是否有其它办法,欢迎指教.

解决方法

  1. 修改session的事务隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. 不断查询满足条件的任务不要放到一个事务里. 发现"affected rows"为0,更新不到数据时, 事务rollback,重新启动事务. 即在循环里不断开启事务而不是在事务里不断循环.

  2. 还有一个办法是开事务然后select for update, 但是这种方法会导致锁表, 必须等待其它事务提交后才能返回. 当初我进行设计的时候,是计划使用select for update的方式的, 但是最终没有使用, 现在回想, 可能是没有开事务, 结果两个实例都查询到了相同的记录, 所以被我否定了. 但是看我另一个文章 <<mysql抢单>>又似乎可能是由于锁表而弃用了, 原因已经不可考了.

但从本个需求来说, 似乎使用select for update来让把表锁住会更简单.

另一个问题

你以为抢到任务的实例就可以高枕无忧了吗, 错了! 等他高高兴兴处理完任务, 要把任务状态置为成功时, 发现这个任务居然被没有抢到任务的实例给锁了, 自已只能得到一个锁超时的错误

2015-11-23 19:42:52|ERR|function.inc.php|113||SQL fail: Lock wait timeout exceeded; try restarting transaction

请期待下一个问题分析.

补充说明

今天回来确认了一下, 实际上ORACEL的update task set status = 1 where status = 0 and rownum = 1 returning taskid 这个SQL也会把表锁住.

所以可以用@flygogo 在30楼提出的方法模拟oracle 的returning

SET @update_id := 0; 
update ae.task set status=1, id = (SELECT @update_id := id) where status=0 limit 1; SELECT @update_id;

oracle

而postgresql的update似乎没有limit 1之类的限定只更新一条的写法?

同时ORACLE和postgresql的select for update 也都会锁表.

所以就本文所讨论的范围来说, 似乎不能说是两个凡是. 叉!

再补充说明

差点被绕晕了. 其实本文所指出的mysql在"REPEATABLE-READ"事务隔离级别下的表现是奇怪的,不直观的,这点值得注意. 明明select出来的数据是可更新, 而更新时候又没有成功, 会让人非常疑惑. 而为oracel在"Serializable"级别下发现数据已经被更新了之后,抛出"ORA-08177"的做法才更直观更合适.

本文另一个意义是分享了一种不锁表实现队列的方法

© 著作权归作者所有

共有 人打赏支持
周翼翼
粉丝 71
博文 17
码字总数 6402
作品 0
深圳
程序员
加载中

评论(44)

z
zhxh007

引用来自“周翼翼”的评论

引用来自“zhxh007”的评论

如果多个字段呢,比如叫hostname
update ae.task set status=1,hostname="传入执行这条sql的ip或者其它能标识这个进程的" where status= 0 limit 500;
然后程序程序查询的时候select * from ae.task where hostname="传入执行这条sql的ip或者其它能标识这个进程的" and status=1
这样查询到自己机器更新的数据,不知道可以不可以!

这样就是不管能不能处理,都先揽过来,就不是分布式了。每次只揽一条才是最合适的。
嗯!这样做可能性能不好,不过一次更新多点儿的话,也还好,实际上sql还有一层,先把要处理的敛过来,同时打上时间戳,别的机器抢的时候,会看之前机器的处理情况,比如发现之前的机器隔了指定的时间,还没处理完,就认为那台机器挂了,就直接抢过来自己处理,处理完后根据id删掉处理的任务,处理失败的就就把本条记录写到别的表,同时删掉,然后由其它进程处理这张失败的表!做重试,之前的系统就是这么搞的!不知道跟你这个像不像
泥沙砖瓦浆木匠
泥沙砖瓦浆木匠

引用来自“逝水fox”的评论

后续更新同个数据的语句与前面查询结果有关,需要select ... for update; 不需要改事务隔离级别。原因在于MySQL InnoDB在REPEATABLE-READ下select默认是用的非锁定一致性读,不加锁,读的undo段的数据拷贝...SERIALIZABLE下默认只是读锁一样有问题。

引用来自“周翼翼”的评论

select for update 会锁表, 这是我不希望看到的. 但确实这个需求让他锁表会更简单.
select for update 会锁表? 锁行也可
mark35
mark35
细节是魔鬼。mysql对于各种功能都支持,等你用的时候就明白啥是要你命3000了。当然当作超级记事本用用还是不错的。
周翼翼
周翼翼

引用来自“宏哥”的评论

引用来自“周翼翼”的评论

引用来自“flygogo”的评论

引用来自“flygogo”的评论

看了一下 ,看到主要说道了两个问题:
1. REPEATABLE-READ 下一直死循环:
这个产生的原因我估计是因为这个事务一直没有结束,应该考虑放到调用事务方法的最外层做循环,一个事务失败了,就再启动一个,可以避免很长的事务,引起更多的问题。

2. 关于 分布式的锁问题, REPEATABLE-READ 和 READ COMMITED 都无法解决此问题,因为你使用的 select id from ae.task where status=0; -- 得到ID 这一步是快照读,无法做到严格意义的两个方面都取出同样的。 而你第二次通过乐观锁的方法 update ae.task set status=1 where id = ${id} and status=0;(此处是主干读) 可以解决此冲突,而你是不是觉得两步可能浪费了?

而使用oracle update ae.task set status=1 where status=0 and rownum = 1 returning id 这种方法是非快照读当然可以解决。

select for update会导致锁表 这个如果是innodb在加上其他索引条件的情况下,本身是不会的,可能是你表数据量本来就少,比如说你所说的只有一条。

另外顺便提一句,简单从这个场景来看 select lock in shared mode 应该是比较适合的,即可以阻止update操作,又不至于读到的是快照数据。

引用来自“周翼翼”的评论

lock in share mode解决不了问题. 多个进程很可能获取到相同的id, 必须要要根据update的结果来判断谁获取到的任务. 同时两个进程同时lock in share mode之后, 谁也update不了,除非两个进程恰好同时update然后引发dead lock,其中一个才会成功. 事情变复杂了,但还是没有解决问题.
lock in share mode的确产生类似的问题,原理和你说的 使用SERIALIZABLE级别产生的问题是一样的。 还是从你的需求出发来看的话,就是觉得oracle提供了返回id的方式很直接。 其实 mysql 也是可以通过类似的方法搞定的: SET @update_id := 0; update ae.task set status=1, id = (SELECT @update_id := id) where status=0 limit 1; SELECT @update_id;

这样还是会锁表。

引用来自“flygogo”的评论

如果你说这样锁表了的话,那么我觉得 同样的结构 同样的数据量,oracle 应该也是锁表的。 就如你说 你只有一条数据,锁升级引起的锁表

引用来自“周翼翼”的评论

我的意思是, 表里有多条满足条件的记录, 你上面说的方法会锁表(虽然limite 1), 而oracle同样的情况只会锁update的那一条

引用来自“宏哥”的评论

postgresql 也只会锁一条 所以, 用postgresql Pl/sql直接实现一个物化队列很简单。

引用来自“周翼翼”的评论

晚上回来重新试了一下, update task set status = 1 where status = 0 and rownum = 1 ORACLE 也是会锁表
update xxx set yyy where id in( select xxxx from table where rownum=1 for update)

有for update 似乎语法错误。没有for update 还是会锁表。我估计是没有主键update导致的锁表。rownum = 1只是对满足where条件的结果集的过滤,而锁表应当发生在返回结果集前。这只是猜测,明天再查查资料。
宏哥
宏哥

引用来自“周翼翼”的评论

引用来自“flygogo”的评论

引用来自“flygogo”的评论

看了一下 ,看到主要说道了两个问题:
1. REPEATABLE-READ 下一直死循环:
这个产生的原因我估计是因为这个事务一直没有结束,应该考虑放到调用事务方法的最外层做循环,一个事务失败了,就再启动一个,可以避免很长的事务,引起更多的问题。

2. 关于 分布式的锁问题, REPEATABLE-READ 和 READ COMMITED 都无法解决此问题,因为你使用的 select id from ae.task where status=0; -- 得到ID 这一步是快照读,无法做到严格意义的两个方面都取出同样的。 而你第二次通过乐观锁的方法 update ae.task set status=1 where id = ${id} and status=0;(此处是主干读) 可以解决此冲突,而你是不是觉得两步可能浪费了?

而使用oracle update ae.task set status=1 where status=0 and rownum = 1 returning id 这种方法是非快照读当然可以解决。

select for update会导致锁表 这个如果是innodb在加上其他索引条件的情况下,本身是不会的,可能是你表数据量本来就少,比如说你所说的只有一条。

另外顺便提一句,简单从这个场景来看 select lock in shared mode 应该是比较适合的,即可以阻止update操作,又不至于读到的是快照数据。

引用来自“周翼翼”的评论

lock in share mode解决不了问题. 多个进程很可能获取到相同的id, 必须要要根据update的结果来判断谁获取到的任务. 同时两个进程同时lock in share mode之后, 谁也update不了,除非两个进程恰好同时update然后引发dead lock,其中一个才会成功. 事情变复杂了,但还是没有解决问题.
lock in share mode的确产生类似的问题,原理和你说的 使用SERIALIZABLE级别产生的问题是一样的。 还是从你的需求出发来看的话,就是觉得oracle提供了返回id的方式很直接。 其实 mysql 也是可以通过类似的方法搞定的: SET @update_id := 0; update ae.task set status=1, id = (SELECT @update_id := id) where status=0 limit 1; SELECT @update_id;

这样还是会锁表。

引用来自“flygogo”的评论

如果你说这样锁表了的话,那么我觉得 同样的结构 同样的数据量,oracle 应该也是锁表的。 就如你说 你只有一条数据,锁升级引起的锁表

引用来自“周翼翼”的评论

我的意思是, 表里有多条满足条件的记录, 你上面说的方法会锁表(虽然limite 1), 而oracle同样的情况只会锁update的那一条

引用来自“宏哥”的评论

postgresql 也只会锁一条 所以, 用postgresql Pl/sql直接实现一个物化队列很简单。

引用来自“周翼翼”的评论

晚上回来重新试了一下, update task set status = 1 where status = 0 and rownum = 1 ORACLE 也是会锁表
update xxx set yyy where id in( select xxxx from table where rownum=1 for update)
周翼翼
周翼翼

引用来自“周翼翼”的评论

引用来自“flygogo”的评论

引用来自“flygogo”的评论

看了一下 ,看到主要说道了两个问题:
1. REPEATABLE-READ 下一直死循环:
这个产生的原因我估计是因为这个事务一直没有结束,应该考虑放到调用事务方法的最外层做循环,一个事务失败了,就再启动一个,可以避免很长的事务,引起更多的问题。

2. 关于 分布式的锁问题, REPEATABLE-READ 和 READ COMMITED 都无法解决此问题,因为你使用的 select id from ae.task where status=0; -- 得到ID 这一步是快照读,无法做到严格意义的两个方面都取出同样的。 而你第二次通过乐观锁的方法 update ae.task set status=1 where id = ${id} and status=0;(此处是主干读) 可以解决此冲突,而你是不是觉得两步可能浪费了?

而使用oracle update ae.task set status=1 where status=0 and rownum = 1 returning id 这种方法是非快照读当然可以解决。

select for update会导致锁表 这个如果是innodb在加上其他索引条件的情况下,本身是不会的,可能是你表数据量本来就少,比如说你所说的只有一条。

另外顺便提一句,简单从这个场景来看 select lock in shared mode 应该是比较适合的,即可以阻止update操作,又不至于读到的是快照数据。

引用来自“周翼翼”的评论

lock in share mode解决不了问题. 多个进程很可能获取到相同的id, 必须要要根据update的结果来判断谁获取到的任务. 同时两个进程同时lock in share mode之后, 谁也update不了,除非两个进程恰好同时update然后引发dead lock,其中一个才会成功. 事情变复杂了,但还是没有解决问题.
lock in share mode的确产生类似的问题,原理和你说的 使用SERIALIZABLE级别产生的问题是一样的。 还是从你的需求出发来看的话,就是觉得oracle提供了返回id的方式很直接。 其实 mysql 也是可以通过类似的方法搞定的: SET @update_id := 0; update ae.task set status=1, id = (SELECT @update_id := id) where status=0 limit 1; SELECT @update_id;

这样还是会锁表。

引用来自“flygogo”的评论

如果你说这样锁表了的话,那么我觉得 同样的结构 同样的数据量,oracle 应该也是锁表的。 就如你说 你只有一条数据,锁升级引起的锁表

引用来自“周翼翼”的评论

我的意思是, 表里有多条满足条件的记录, 你上面说的方法会锁表(虽然limite 1), 而oracle同样的情况只会锁update的那一条

引用来自“宏哥”的评论

postgresql 也只会锁一条 所以, 用postgresql Pl/sql直接实现一个物化队列很简单。
晚上回来重新试了一下, update task set status = 1 where status = 0 and rownum = 1 ORACLE 也是会锁表
瑞兹
瑞兹
我觉得还是用消息服务器进行分发比较好,把符合条件的数据查出来放到消息队列里面,多跑几个消费者效果是一样的,并且不会出现上面的问题
宏哥
宏哥

引用来自“周翼翼”的评论

引用来自“flygogo”的评论

引用来自“flygogo”的评论

看了一下 ,看到主要说道了两个问题:
1. REPEATABLE-READ 下一直死循环:
这个产生的原因我估计是因为这个事务一直没有结束,应该考虑放到调用事务方法的最外层做循环,一个事务失败了,就再启动一个,可以避免很长的事务,引起更多的问题。

2. 关于 分布式的锁问题, REPEATABLE-READ 和 READ COMMITED 都无法解决此问题,因为你使用的 select id from ae.task where status=0; -- 得到ID 这一步是快照读,无法做到严格意义的两个方面都取出同样的。 而你第二次通过乐观锁的方法 update ae.task set status=1 where id = ${id} and status=0;(此处是主干读) 可以解决此冲突,而你是不是觉得两步可能浪费了?

而使用oracle update ae.task set status=1 where status=0 and rownum = 1 returning id 这种方法是非快照读当然可以解决。

select for update会导致锁表 这个如果是innodb在加上其他索引条件的情况下,本身是不会的,可能是你表数据量本来就少,比如说你所说的只有一条。

另外顺便提一句,简单从这个场景来看 select lock in shared mode 应该是比较适合的,即可以阻止update操作,又不至于读到的是快照数据。

引用来自“周翼翼”的评论

lock in share mode解决不了问题. 多个进程很可能获取到相同的id, 必须要要根据update的结果来判断谁获取到的任务. 同时两个进程同时lock in share mode之后, 谁也update不了,除非两个进程恰好同时update然后引发dead lock,其中一个才会成功. 事情变复杂了,但还是没有解决问题.
lock in share mode的确产生类似的问题,原理和你说的 使用SERIALIZABLE级别产生的问题是一样的。 还是从你的需求出发来看的话,就是觉得oracle提供了返回id的方式很直接。 其实 mysql 也是可以通过类似的方法搞定的: SET @update_id := 0; update ae.task set status=1, id = (SELECT @update_id := id) where status=0 limit 1; SELECT @update_id;

这样还是会锁表。

引用来自“flygogo”的评论

如果你说这样锁表了的话,那么我觉得 同样的结构 同样的数据量,oracle 应该也是锁表的。 就如你说 你只有一条数据,锁升级引起的锁表

引用来自“周翼翼”的评论

我的意思是, 表里有多条满足条件的记录, 你上面说的方法会锁表(虽然limite 1), 而oracle同样的情况只会锁update的那一条
postgresql 也只会锁一条 所以, 用postgresql Pl/sql直接实现一个物化队列很简单。
周翼翼
周翼翼

引用来自“周翼翼”的评论

引用来自“flygogo”的评论

引用来自“flygogo”的评论

看了一下 ,看到主要说道了两个问题:
1. REPEATABLE-READ 下一直死循环:
这个产生的原因我估计是因为这个事务一直没有结束,应该考虑放到调用事务方法的最外层做循环,一个事务失败了,就再启动一个,可以避免很长的事务,引起更多的问题。

2. 关于 分布式的锁问题, REPEATABLE-READ 和 READ COMMITED 都无法解决此问题,因为你使用的 select id from ae.task where status=0; -- 得到ID 这一步是快照读,无法做到严格意义的两个方面都取出同样的。 而你第二次通过乐观锁的方法 update ae.task set status=1 where id = ${id} and status=0;(此处是主干读) 可以解决此冲突,而你是不是觉得两步可能浪费了?

而使用oracle update ae.task set status=1 where status=0 and rownum = 1 returning id 这种方法是非快照读当然可以解决。

select for update会导致锁表 这个如果是innodb在加上其他索引条件的情况下,本身是不会的,可能是你表数据量本来就少,比如说你所说的只有一条。

另外顺便提一句,简单从这个场景来看 select lock in shared mode 应该是比较适合的,即可以阻止update操作,又不至于读到的是快照数据。

引用来自“周翼翼”的评论

lock in share mode解决不了问题. 多个进程很可能获取到相同的id, 必须要要根据update的结果来判断谁获取到的任务. 同时两个进程同时lock in share mode之后, 谁也update不了,除非两个进程恰好同时update然后引发dead lock,其中一个才会成功. 事情变复杂了,但还是没有解决问题.
lock in share mode的确产生类似的问题,原理和你说的 使用SERIALIZABLE级别产生的问题是一样的。 还是从你的需求出发来看的话,就是觉得oracle提供了返回id的方式很直接。 其实 mysql 也是可以通过类似的方法搞定的: SET @update_id := 0; update ae.task set status=1, id = (SELECT @update_id := id) where status=0 limit 1; SELECT @update_id;

这样还是会锁表。

引用来自“flygogo”的评论

如果你说这样锁表了的话,那么我觉得 同样的结构 同样的数据量,oracle 应该也是锁表的。 就如你说 你只有一条数据,锁升级引起的锁表
我的意思是, 表里有多条满足条件的记录, 你上面说的方法会锁表(虽然limite 1), 而oracle同样的情况只会锁update的那一条
flygogo
flygogo

引用来自“周翼翼”的评论

引用来自“flygogo”的评论

引用来自“flygogo”的评论

看了一下 ,看到主要说道了两个问题:
1. REPEATABLE-READ 下一直死循环:
这个产生的原因我估计是因为这个事务一直没有结束,应该考虑放到调用事务方法的最外层做循环,一个事务失败了,就再启动一个,可以避免很长的事务,引起更多的问题。

2. 关于 分布式的锁问题, REPEATABLE-READ 和 READ COMMITED 都无法解决此问题,因为你使用的 select id from ae.task where status=0; -- 得到ID 这一步是快照读,无法做到严格意义的两个方面都取出同样的。 而你第二次通过乐观锁的方法 update ae.task set status=1 where id = ${id} and status=0;(此处是主干读) 可以解决此冲突,而你是不是觉得两步可能浪费了?

而使用oracle update ae.task set status=1 where status=0 and rownum = 1 returning id 这种方法是非快照读当然可以解决。

select for update会导致锁表 这个如果是innodb在加上其他索引条件的情况下,本身是不会的,可能是你表数据量本来就少,比如说你所说的只有一条。

另外顺便提一句,简单从这个场景来看 select lock in shared mode 应该是比较适合的,即可以阻止update操作,又不至于读到的是快照数据。

引用来自“周翼翼”的评论

lock in share mode解决不了问题. 多个进程很可能获取到相同的id, 必须要要根据update的结果来判断谁获取到的任务. 同时两个进程同时lock in share mode之后, 谁也update不了,除非两个进程恰好同时update然后引发dead lock,其中一个才会成功. 事情变复杂了,但还是没有解决问题.
lock in share mode的确产生类似的问题,原理和你说的 使用SERIALIZABLE级别产生的问题是一样的。 还是从你的需求出发来看的话,就是觉得oracle提供了返回id的方式很直接。 其实 mysql 也是可以通过类似的方法搞定的: SET @update_id := 0; update ae.task set status=1, id = (SELECT @update_id := id) where status=0 limit 1; SELECT @update_id;

这样还是会锁表。
如果你说这样锁表了的话,那么我觉得 同样的结构 同样的数据量,oracle 应该也是锁表的。 就如你说 你只有一条数据,锁升级引起的锁表
Mysql的锁的问题

1、在可视化窗口执行select...for update,打开另一个窗口,对同一行数据进行update,是可以正常操作的。查询才知道,窗口的select是自动进行事务提交的,如果要是for update生效,需要先执行...

carmen-ly
2016/11/24
17
0
MySQL数据库事务隔离级别(Transaction Isolation Level)

数据库隔离级别有四种,应用《高性能mysql》一书中的说明: 然后说说修改事务隔离级别的方法: 1.全局修改,修改mysql.ini配置文件,在最后加上 1 #可选参数有:READ-UNCOMMITTED, READ-COMM...

嘻哈开发者
2016/01/06
79
0
数据库事务的四大特性以及事务的隔离级别

本篇讲诉数据库中事务的四大特性(ACID),并且将会详细地说明事务的隔离级别。   如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ 原子性(Atomicity)  原...

1071954237
2017/10/27
0
0
mysql死锁问题分析

mysql死锁问题分析 源贴:https://www.cnblogs.com/LBSer/p/5183300.html 线上某服务时不时报出如下异常(大约一天二十多次):“Deadlock found when trying to get lock;”。 Oh, My God! ...

rshare
2017/12/06
0
0
常见并发问题

重复添加购物车 背景 购物车中同一商品只能有一条记录 添加购物车时,如果商品已经存在,则在原来的数量上增加;如果不存在,则insert一条数据 错误逻辑    添加购物车流程图 时间线示意图...

齐晋
2017/04/12
0
0
mysql死锁问题分析

线上某服务时不时报出如下异常(大约一天二十多次):“Deadlock found when trying to get lock;”。 Oh, My God! 是死锁问题。尽管报错不多,对性能目前看来也无太大影响,但还是需要解决,...

毛爷爷夸我帅
2016/05/16
80
1
浅谈数据库事物的四大特性(ACID)以及事物的隔离级别

事物的四大特性  如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ 原子性(Atomicity)   原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因...

HenrySun
2016/08/20
107
0
mysql锁技术讨论

mysql 常见锁问题分析 1 参考资料 - The InnoDB Transaction Mode and Locking-官方文档- MySQL 加锁处理分析- Innodb中的事务隔离级别和锁的关系 2 要明确的概念 - 不可重复读和幻读的区别-...

乒乓狂魔
2016/02/23
635
0
事务隔离级别

在SQL标准中定义了四种隔离级别,每种隔离级别都规定了一个事务中所做的修改,哪些事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。 *每...

青春掌柜
2016/05/26
0
0
Mysql数据库理论基础之九---四类隔离级别

一、简介 由MySQL AB公司开发,是最流行的开放源码SQL数据库管理系统,主要特点: 1、是一种数据库管理系统 2、是一种关联数据库管理系统 3、是一种开放源码软件,且有大量可用的共享MySQL软...

风过_无痕
2017/06/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Java基础——异常

声明:本栏目所使用的素材都是凯哥学堂VIP学员所写,学员有权匿名,对文章有最终解释权;凯哥学堂旨在促进VIP学员互相学习的基础上公开笔记。 异常处理: 可以挖很多个陷阱,但是不要都是一样...

凯哥学堂
30分钟前
0
0
180723-Quick-Task 动态脚本支持框架之结构设计篇

文章链接:https://liuyueyi.github.io/hexblog/2018/07/23/180723-Quick-Task-动态脚本支持框架之结构设计篇/ Quick-Task 动态脚本支持框架之结构设计篇 相关博文: 180702-QuickTask动态脚本...

小灰灰Blog
33分钟前
0
0
SBT 常用开发技巧

SBT 一直以来都是 Scala 开发者不可言说的痛,最主要的原因就是官方文档维护质量较差,没有经过系统的、循序渐进式的整理,导致初学者入门门槛较高。虽然也有其它构建工具可以选择(例如 Mill...

joymufeng
38分钟前
0
0
HBase in Practice - 性能、监控及问题解决

李钰(社区ID:Yu Li),阿里巴巴计算平台事业部高级技术专家,HBase开源社区PMC&committer。开源技术爱好者,主要关注分布式系统设计、大数据基础平台建设等领域。连续4年基于HBase/HDFS设计和...

中国HBase技术社区
39分钟前
1
0
ES18-JAVA API 批量操作

1.批量查询 Multi Get API public static void multiGet() {// 批量查询MultiGetResponse response = getClient().prepareMultiGet().add("my_person", "my_index", "1")// 查......

贾峰uk
43分钟前
0
0
SpringBoot2.0使用health

1,引入actuator <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency> 2,application.properties ......

暗中观察
50分钟前
0
0
阿里巴巴Java开发规约

###编程规约 命名风格 【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束 【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。...

简心
55分钟前
0
0
如何用TypeScript来创建一个简单的Web应用

转载地址 如何用TypeScript来创建一个简单的Web应用 安装TypeScript 获取TypeScript工具的方式: 通过npm(Node.js包管理器) npm install -g typescript 构建你的第一个TypeScript文件 创建...

durban
59分钟前
0
0
分享好友,朋友圈自定义分享链接无效

这个问题是微信6.5.6版本以后,修改了分享规则:分享的连接必须在公众号后台设定的js安全域名内

LM_Mike
今天
0
0
2018年7月23日课程

一、LVS-DR介绍 director分配请求到不同的real server。real server 处理请求后直接回应给用户,这样director负载均衡器仅处理客户机与服务器的一半连接。负载均衡器仅处理一半的连接,避免了...

人在艹木中
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部