question about "Race condition on MySQL epic fail"
时间:2014-07-21
来源:互联网
http://computer.discuss.com.hk/v ... &extra=page%3D5
I capture one page from "Race condition on MySQL epic fail".
But I found the result is correct as "balance = -80",
Anyone can explain that to me?
2014-6-24 10:00 PM
2014-6-24 10:00 PM
作者: form5 发布时间: 2014-07-21
'CREATE TABLE `user_balance` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n `username` varchar(45) DEFAULT NULL,\n `balance` int(11) DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8'
作者: form5 发布时间: 2014-07-21
the following link is puzzling me.
http://computer.discuss.com.hk/viewthread.php?tid=23107538&extra=page%3D5
I capture one page from "Race condition on MySQL epic fail".
But I found the result ...
http://dev.mysql.com/doc/refman/5.6/en/set-transaction.html
http://dev.mysql.com/doc/refman/5.6/en/innodb-lock-modes.html
http://dev.mysql.com/doc/refman/5.6/en/innodb-consistent-read.html
Isolation level "REPEATABLE READ" is ok, but the select statement is wrong.
"SELECT ... FOR UPDATE" should be used instead of plain "SELECT".
The first "SELECT ... FOR UPDATE" will block others until the corresponding transaction ends.
作者: xianrenb 发布时间: 2014-07-21
And I'm not really familiar with MySQL in production environment. But if this isolation level is set or by default, Oracle would just do the same...
Probably need to read the context for why:
1. Why was repeatable read isolation level being used? What is the default?
2. Why wasn't select....for update being used? Is that because it just want to demonstrate a long spanning transaction involved UI? Just not a perfect example or what?
I didn't read it thoroughly enough as I'm not familiar with MySQL that I won't be qualified to digest it....
the following link is puzzling me.
http://computer.discuss.com.hk/viewthread.php?tid=23107538&extra=page%3D5
I capture one page from "Race condition on MySQL epic fail".
But I found the result ...
作者: a8d7e8 发布时间: 2014-07-21
after having fired 1st set, then, within 10 sec fire 2nd set.
you will see the result.
作者: stevie1 发布时间: 2014-07-21
I think it want to say that the checking for non-negative balance fail....
And I'm not really familiar with MySQL in production environment. But if this isolation level is set or by default, Oracle ...
I believe the only safe isolation level should be "SERIALIZABLE".
But with correct "SELECT ... FOR UPDATE" and "SELECT ... LOCK IN SHARE MODE" statements, "REPEATABLE READ" should act the same as in "SERIALIZABLE".
"SELECT ... FOR UPDATE" is just using write/exclusive lock for select.
"SELECT ... LOCK IN SHARE MODE" is just using read/shared lock for select.
When one wants to write/update records, he should use write lock, just like opening files in write mode.
作者: xianrenb 发布时间: 2014-07-21

Perhaps repeatable read affects update as well?
such that when read get 100, update balance = balance - 90 would read as 100 too in Oracle.
Which isn't the case for MySQL so the presentation say MySQL fail......
I think it want to say that the checking for non-negative balance fail....
And I'm not really familiar with MySQL in production environment. But if this isolation level is set or by default, Oracle ...
作者: a8d7e8 发布时间: 2014-07-21
Shit I misread MVCC as application layer approach. That's why MVCC wasn't elaborated much as it's a feature of PostgreSQL.....
The MySQL epic fail is a major point to show how "bad" MySQL is comparing to PostgreSQL, even when performance is neglected.
作者: a8d7e8 发布时间: 2014-07-21
the following link is puzzling me.
http://computer.discuss.com.hk/viewthread.php?tid=23107538&extra=page%3D5
I capture one page from "Race condition on MySQL epic fail".
But I found the result ...
作者: a8d7e8 发布时间: 2014-07-21
Oh the presentation is to pro-PostgreSQL that it uses MVCC as the backbone for the isolation level that is highly efficient for high concurrency usage.
Shit I misread MVCC as application layer appr ...
http://en.wikipedia.org/wiki/Multiversion_concurrency_control
http://docs.oracle.com/cd/E11882_01/server.112/e26088/ap_locks001.htm#SQLRF55506
http://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_10002.htm#SQLRF01702
http://www.postgresql.org/docs/9.1/static/explicit-locking.html
http://www.postgresql.org/docs/9.0/static/sql-select.html
Based on what I skimmed through above, I would say the presentation was quite misleading.
MySQL InnoDB is with MVCC, and the example should use SELECT ... FOR UPDATE for that kind of application.
作者: xianrenb 发布时间: 2014-07-21
you may want to put a "sleep" before the commit statement in "machine 1". say, 10 sec.
after having fired 1st set, then, within 10 sec fire 2nd set.
you will see the result.
start transaction;
select balance from user_balance where username = 'Person X';
update user_balance set balance = balance - 90 where username = 'Person X';
select sleep(10);
commit;
anyway, the order of steps are clearly stated in the given picture,
if Machine B runs first, the order of commands in two given sessions is illustrated in this picture. 1->2->3->4->5->6->7->8->9->10
the result is the same as (2->1->4->3->6->5->7-> 8->9->10).
did you try that out before come to any conclusion?
[ 本帖最后由 form5 於 2014-6-25 10:48 PM 编辑 ]
2014-6-25 10:06 PM
2014-6-25 10:06 PM
作者: form5 发布时间: 2014-07-21
I think it want to say that the checking for non-negative balance fail....
And I'm not really familiar with MySQL in production environment. But if this isolation level is set or by default, Oracle ...
作者: form5 发布时间: 2014-07-21
Oh I didn't really try and rarely use repeatable read isolation level.

Perhaps repeatable read affects update as well?
such that when read get 100, update balance = balance - 90 would read as ... [/quote
]
if one replace the isolation level "serializable" here, "dead lock" happens

作者: form5 发布时间: 2014-07-21
"SELECT ... FOR UPDATE" is just using write/exclusive lock for select.
"SELECT ... LOCK IN SHARE MODE" is just using read/shared lock for select.
作者: form5 发布时间: 2014-07-21
btw, did you use exactly the same version of MySQL as in the presentation?
作者: form5 发布时间: 2014-07-21
[quote]原帖由 a8d7e8 於 2014-6-25 02:29 PM 发表

Oh I didn't really try and rarely use repeatable read isolation level.

Perhaps repeatable r ...
The plain SELECT statement would act as "SELECT ... LOCK IN SHARE MODE", meaning shared lock would be used, while the UPDATE statement uses exclusive lock.
Two machines could both go through the shared lock, but get deadlock at the UPDATE statement.
作者: xianrenb 发布时间: 2014-07-21
yep, part of my question is why using this particular example to demonstrate race condition?
作者: a8d7e8 发布时间: 2014-07-21
1. MVCC vs traditional READ-WRITE lock isolation.
2. How good is MVCC vs traditional RDBMS in different isolation level.
3. MySQL MVCC fails, whereas PostgreSQL is not.
For (1),
Comparison matrix of different isolation level for traditional RDBMS vs MVCC:
Read committed: [Write lock] vs [Write lock]
Repeatable read: [read lock until end of TX] vs [(no read lock), (write lock or update exception)]
Serialiable: [lock all] vs [repeatable read + monitor, highly cpu intensive]
Isolation level facts:
i) MySQL defaults in repeatable read isolation level.
ii) Oracle defaults in read committed isolation level.
iii) Oracle has no repeatable read, whereas the author refer to serialiable instead.
Then, for (2), we have:
There is only obvious advantage in repeatable read since there is no read lock under MVCC approach.
And then (3),
MySQL will finish the race condition. balance -> -80
Oracle(in Serializable mode) will not permit the second update as the read in tx2 blocks until tx1 finish. balance -> 10 (one tx follow application logic to not update)
PostgreSQL, "In WRITE, if target rows have timestamp > start of current TX, exception will be raised.". balance -> 10 (one tx will finish with exception!)
In conclusion, MVCC(PostgreSQL) fails when conflict is found rather than using lock to prevent the conflict which is supreme in high concurrency environment.
ie. No need to use select....for update at all, just let the race condition fail and handle that from application. Well kinda like optimistic locking in ORM..
[ 本帖最后由 a8d7e8 於 2014-6-26 03:19 PM 编辑 ]
作者: a8d7e8 发布时间: 2014-07-21
Ok lets focus on the "Today will cover" pts:
1. MVCC vs traditional READ-WRITE lock isolation.
2. How good is MVCC vs traditional RDBMS in different isolation level.
3. MySQL MVCC fails, whereas ...
The race condition would have no deadlock as only the UPDATE statement uses exclusive lock.
In other words, the balance could go negative even if the application checks it before issuing the UPDATE statement.
To make it working correctly, "SELECT ... FOR UPDATE" should be used no matter what the isolation level is.
It would make sure that all other transactions that would update the balance end, before continuing the process (checking the balance amount).
I believe another method that does not require using "SELECT ... FOR UPDATE" is to list all the related/expected values in the WHERE clause of the UPDATE statement.
Such usage should be similar to SQL statements without transaction.
Update would only occur when there is no change between the SELECT and UPDATE statements
作者: xianrenb 发布时间: 2014-07-21
Maybe I run the epic fail in pg to shows the spirit of the author:
-- 1. open 2 sessions
-- 2. Run this in any session
create table user_balance (
name text,
balance integer
);
insert into user_balance values ('test', 100);
---
-- 3. Run this in session 1
begin;
set transaction isolation level repeatable read;
select * from user_balance where name = 'test';
update user_balance set balance = balance - 90 where name = 'test';
--
-- 4. Run this in session 2
begin;
set transaction isolation level repeatable read;
select * from user_balance where name = 'test';
update user_balance set balance = balance - 90 where name = 'test';
-- session 2 blocks here
-- 5. Run this in session 1
commit;
--
-- 6. In session 2
-- ERROR: could not serialize access due to concurrent update
commit;
-- result in ROLLBACK
I believe the default isolation level in MYSQL would have no lock for plain SELECT statement.
The race condition would have no deadlock as only the UPDATE statement uses exclusive lock.
In other ...
作者: a8d7e8 发布时间: 2014-07-21
So the spirits are:
1. The author prefers repeatable read isolation level on PostgreSQL over all other RDBMS and isolation level combinations;
2. The MVCC implementation in PostgreSQL for repeatable read isolation level is a little bit different from that of MySQL that making the choice of MySQL a fail;
3. PostgreSQL is seemingly the best choice for supporting high concurrency application that needs ACID as well.
Perhaps there're more reasons to choose PostgreSQL among others but just this point is enough for an internet service based start-up whereas high concurrency is the most critical non-functional requirement.
作者: a8d7e8 发布时间: 2014-07-21
Oh I just found that I've PostgreSQL 9.3 installed.
Maybe I run the epic fail in pg to shows the spirit of the author:
-- 1. open 2 sessions
-- 2. Run this in any session
create table user_b ...
--------------set up--------------
create table `user_balance` (
`name` text,
`balance` int(11) DEFAULT NULL
) engine = InnoDB;
insert into user_balance values ('test', 100);
SELECT * FROM user_balance;
step 1.
------------- session 1-----------------
set transaction isolation level repeatable read;
start transaction;
select balance from user_balance where name = 'test' for update;
update user_balance set balance = balance - 90 where name = 'test';
step 2.
----------------session 2-----------------------------
set transaction isolation level repeatable read;
start transaction;
select balance from user_balance where name = 'test' for update;
update user_balance set balance = balance - 90 where name = 'test';
step 3
---------------session 1-----------------
commit;
step 4
---------------session 2-----------------
commit;
result => balance = -80, "select ... for update" still fails.
作者: form5 发布时间: 2014-07-21
-- 3. Run this in session 1
begin;
set transaction isolation level repeatable read;
select * from user_balance where name = 'test';
update user_balance set balance = balance - 90 where name = 'test';
--
-- 4. Run this in session 2
begin;
set transaction isolation level repeatable read;
select * from user_balance where name = 'test';
update user_balance set balance = balance - 90 where name = 'test';
-- session 2 blocks here
-- 5. Run this in session 1
commit;
--


作者: ceap2003 发布时间: 2014-07-21
The application has to check the balance value before continuing.
I guess it is to check whether the value is greater than 90.
Correct design should have no deadlock.
One could design a correct locking sequence for a transaction.
I believe one would consider only using exclusive lock(s) or only using shared lock(s) (hence no UPDATE) within a single transaction as a best practice.
[ 本帖最后由 xianrenb 於 2014-6-27 09:50 AM 编辑 ]
作者: xianrenb 发布时间: 2014-07-21
From this I can feel the powerful "feature" of PostgreSQL's MVCC backed repeatable read isolation level that the PL/pgSQL developer need no special concurrent mindset but just exception handling during update.
Should step 5: commit the tx1 move to step 3? As the example show that the tx1 is committed before tx2 begin to update

作者: a8d7e8 发布时间: 2014-07-21
Sorry my mistake but the result is the same except that tx2 didn't block and fail instantly.
From this I can feel the powerful "feature" of PostgreSQL's MVCC backed repeatable read isolation level ...
Without proper locking, the transaction may not process in correct sequence.
作者: xianrenb 发布时间: 2014-07-21
set transaction isolation level repeatable read;
start transaction;
select balance from user_balance where username = 'Person X';
update user_balance set balance = balance - 90 where username = ...
By doing so, the sequence must be 6->7->8->5->9->10(or 5->9->10->6->7->8), because 5 must wait 8 to finish.
And "SELECT ... FOR UPDATE" in 5 must report the correct balance value after the first transaction, and hence the operation must be correct (actually the application should decide not to run the second UPDATE) and with no deadlock.
[ 本帖最后由 xianrenb 於 2014-6-27 11:48 AM 编辑 ]
作者: xianrenb 发布时间: 2014-07-21
So it means MySQL (InnoDB) and Oracle uses MVCC for the snapshot feature only whilst PostgreSQL uses MVCC for concurrent write checking as well under repeatable read isolation level.
The keys are:
1. No need to use select for update in PostgreSQL under repeatable read isolation level.
2. Need to use select for update in MySQL (InnoDB) and Oracle as you suggest.
I believe no matter whether one uses MySQL (InnoDB), PostgreSQL or Oracle, all of them are with MVCC, and "SELECT ... FOR UPDATE" or similar statement should be used in order to use exclusive lock ...
作者: a8d7e8 发布时间: 2014-07-21
Sorry my mistake but the result is the same except that tx2 didn't block and fail instantly.
From this I can feel the powerful "feature" of PostgreSQL's MVCC backed repeatable read isolation level ...
Edit : I have tried a8d7e8 example on postgreSQL 9.3.4, whether commit of tx1 in step 3 or step 5, the results are the same, that is as a8d7e8 said that the postgreSQL successfully keep the balance (=10 and tx2 raise error when try to execute the update statement) to be correct, without issue any lock statements explicitly

[ 本帖最后由 ceap2003 於 2014-6-27 02:01 PM 编辑 ]
作者: ceap2003 发布时间: 2014-07-21
- It didn't block.
- It failed instantly.
I'm glad that you spot and reported my mistakes.

But if the tx2 didn't block and fail instantly, then the update statement of tx2 executed successfully? and the result will be same as MySQL (i.e. balance = -80) ?
Edit : I have tried a8d7e8 ex ...
作者: a8d7e8 发布时间: 2014-07-21
It's real shit, as long as "repeatable read" is the default isolation level for mysql inno!!!!
I must be trapped then if I'm to use the same mindset as using Oracle, oh!EDIT: Oh again I type faster than think, yes there should be checking after select....for update......as 闲人 said. So it is the expected effect, even in read committed isolation level, for Oracle.
you know what, shit happens again with "select * for update" under "repeatable read" in mysql
--------------set up--------------
create table `user_balance` (
`name` text,
`balance` ...
作者: a8d7e8 发布时间: 2014-07-21
There is another way to do that is append "nowait" keyword at the end of select statement, i.e. select ... for update nowait,
-- section 1 (tx1)
select * from user_balance where name = 'test' for update nowait;
-- section 2 (tx2)
select * from user_balance where name = 'test' for update nowait; // fail immediately
作者: ceap2003 发布时间: 2014-07-21
"SELECT ... FOR UPDATE" (exclusive lock) should be used instead of plain SELECT.
By doing so, the sequence must be 6->7->8->5->9->10(or 5->9->10->6->7->8), because 5 must wait 8 to finish.
And " ...
- assume that isolation is repeatable read in mysql;
- initial balance is 100
time session1 session2 remarks t+1 start transaction; t+2 start transaction; t+3 SELECT balance FROM user_balance where username = 'Person X' for update; block other select balance ... for update t+5 SELECT balance FROM user_balance where username = 'Person X' for update;session 2, select is blocked t+6 update user_balance set balance = balance - 90 where username = 'Person X'; session 2, select is still blocked t+7 session 2, select is still blocked t+9 commit; session 2, select will run after "session 1, commit"
balance = 10 in session 2 t+10 ****-------check if balance is negative-------*** t+11 update user_balance set balance = balance - 90 where username = 'Person X';
t+12 commit; balance = -80
yes, exit condition should be checked after "select"( t+10), either in sql statements or in application, the original demonstration use "select *" without requesting X lock may not block others, "race condition" seems inevitable.
[ 本帖最后由 form5 於 2014-6-28 02:06 AM 编辑 ]
作者: form5 发布时间: 2014-07-21
有好多人会说为何不用select for update,我只想说:这epic fail是用来证明MySQL的MVCC本身是有bug的,违反了ISO SQL的定义
根据ISO SQL对repeatable read的定义
在transaction begin后read过的record,在整个transaction中都应该维持原本的value
所以User B他过了validation的select statement, 即使不用select for update这种lock promotion,都应该不会concurrency被user A改到还commit 成功的
PS: Oracle / PostgreSQL在User B commit时会出error. cannot preform serialiable transaction(大约吧,我忘了exact的message)
作者: TritonHo 发布时间: 2014-07-21
http://opensource.hk/monthly-talks-2014-07
今次会玩RESTful API
作者: TritonHo 发布时间: 2014-07-21
Oracle / PostgreSQL在User B commit时会出error. cannot preform serialiable transaction(大约吧,我忘了exact的message)
作者: ceap2003 发布时间: 2014-07-21
Oracle claim自己挡phantom read只是让自己tx睇唔到phantom rows,不等於phantom rows不能被加入tables.
所以会出write skew这种race condition:
http://en.wikipedia.org/wiki/Sna ... _Snapshot_Isolation
所以oracle claim的serializable只是真正身份只是repeatable read.
(PS:PostgreSQL的repeatable read正正等於oracle的serializable,而postgreSQL的serializable是在repeatable read之上再开predicate monitering去挡phantom rows)
作者: TritonHo 发布时间: 2014-07-21
Oracle的serializable tx是挡唔住phantom read的
Oracle claim自己挡phantom read只是让自己tx睇唔到phantom rows,不等於phantom rows不能被加入tables.
所以会出write skew这种race condition:
http://en.wi ...
作者: a8d7e8 发布时间: 2014-07-21
另外......
http://opensource.hk/monthly-talks-2014-07
今次会玩RESTful API
作者: a8d7e8 发布时间: 2014-07-21
我是Slide的作者。
有好多人会说为何不用select for update,我只想说:这epic fail是用来证明MySQL的MVCC本身是有bug的,违反了ISO SQL的定义
根据ISO SQL对repeatable read的定义
在transaction begin后r ...
ERROR: could not serialize access due to concurrent updateSQL state: 40001
作者: form5 发布时间: 2014-07-21
Oracle的serializable tx是挡唔住phantom read的
Oracle claim自己挡phantom read只是让自己tx睇唔到phantom rows,不等於phantom rows不能被加入tables.
所以会出write skew这种race condition:
http://en.wi ...
for MySQL, the below pages show that InnoDB will detect deadlook and abort the involved transactions 防止 write skew:http://www.evanjones.ca/db-isolation-semantics.html
for verify, 我自己也试了一次,结果如以上pages 所述
2014-7-2 10:09 PM
2014-7-2 10:09 PM
2014-7-2 10:09 PM
2014-7-2 10:09 PM
作者: ceap2003 发布时间: 2014-07-21
我是Slide的作者。
有好多人会说为何不用select for update,我只想说:这epic fail是用来证明MySQL的MVCC本身是有bug的,违反了ISO SQL的定义
根据ISO SQL对repeatable read的定义
在transaction begin后r ...
其实应该与 locking 、 isolation level 无一定关系。
MySQL 、 PosgresQL 与 Oracle 都是 with MVCC 的。
#41 列出过的 http://www.evanjones.ca/db-isolation-semantics.html 及 http://www.postgresql.org/docs/9.1/static/transaction-iso.html 都指出 PosgresQL 的 default isolation level 是 Read Committed 。
Oracle 亦是类似。
如果真要比较,应该拿相同的 isolation level 出来。
而以我的理解,原 slide 中的 example 是 plain SELECT + UPDATE statements ,亦即是 non-locking read + locking write 。
以 PosgresQL 及 Oracle 的 isolation level 来看,应该是 no deadlock/error message 才对?
p.s.
或者按 documentation , PosgresQL 与 Oracle 出现 deadlock/error message 亦是正常的。
http://www.postgresql.org/docs/9.1/static/explicit-locking.html
我估计 PosgesQL 中, plain SELECT 会取 ACCESS SHARE lock , UPDATE 会取 ROW EXCLUSIVE lock 。
所以就不相容。
http://docs.oracle.com/cd/E11882_01/server.112/e26088/ap_locks001.htm#SQLRF55505
我估计 Oracle 中, plain SELECT 有取 RS lock , UPDATE 有取 RX lock 。
所以就不相容。
[ 本帖最后由 xianrenb 於 2014-7-3 09:17 AM 编辑 ]
作者: xianrenb 发布时间: 2014-07-21
我认为 MVCC 只是一种 implementation 方法,大概只是 database 内部可以同时有多个 version 的 (record) data 而已。
其实应该与 locking 、 isolation level 无一定关系。
MySQL 、 PosgresQL 与 Oracle 都是 ...
MySQL 之所以不同,以我看是因为它在 default isolation level 中有真正的 non-locking read 。
所以 example 中会完全没有 deadlock/error message 。
但其实以上述 example 的情况而言,正确/小心使用的话, 在上述三款常见 database 中都应使用 SELECT ... FOR UPDATE 来选定适当的 locking 方法 。
因为 UPDATE 是 write ,应用 exclusive 的 lock 。
相应的 SELECT (read) ,亦应选用 exclusive 的 lock ,而不是 shared 的。
作者: xianrenb 发布时间: 2014-07-21
而以我的理解,原 slide 中的 example 是 plain SELECT + UPDATE statements ,亦即是 non-locking read + locking write 。
以 PosgresQL 及 Oracle 的 isolation level 来看,应该是 no deadlock/error message 才对?
PostgreSql 在 repeatable read 和 serializable isolation level 会有 error message。
有 error message 是合理的,因为假设 application 会在 select statement 之后会对取出来的 value 进行处理。但在处理的过程中,另一个 transaction 已经把资料更改了。所以 tx1 所做的处理可能是无意义的,故 error
作者: ceap2003 发布时间: 2014-07-21
但其实以上述 example 的情况而言,正确/小心使用的话, 在上述三款常见 database 中都应使用 SELECT ... FOR UPDATE 来选定适当的 locking 方法 。
作者: ceap2003 发布时间: 2014-07-21
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28