문제

I'm running some simple scripts to test possible solutions for an integrity problem I'm solving. Suppose I have a table my_table

|foo     |
|1       |

And I have these two snippets:

// db_slow.php
<?php
$db = new PDO('mysql:host=localhost;dbname=my_playground;charset=utf8', 'root', '');
echo 'starting transaction<br />';
$db->beginTransaction();
$stmt = $db->query('select * from my_table for update');
$rows = $stmt->fetchAll();
echo 'count tables: ', count($rows), '<br />';
if (count($rows) == 1) {
    sleep(10);
    $db->query('insert into my_table(foo) VALUES(2)');
}
$db->commit();
echo 'done';

// db_fast.php
<?php
$db = new PDO('mysql:host=localhost;dbname=my_plyaground;charset=utf8', 'root', '');
echo 'starting transaction<br />';
$db->beginTransaction();
$stmt = $db->query('select * from my_table for update');
$rows = $stmt->fetchAll();
echo 'count tables: ', count($rows), '<br />';
if (count($rows) == 1) {
    $db->query('insert into my_table(foo) VALUES(3)');
}
$db->commit();
echo 'done';

db_slow.php has a 10 second delay to simulate a race condition.

As I understand, select ... for update locks all rows it selects. If I run db_slow then db_fast, db_fast also has a 10 second delay, as it's waiting for db_slow as I expect.

However, what I don't get is this is the output:

// db_slow.php
starting transaction
count tables: 1
done

// db_fast.php
starting transaction
count tables: 2
done

And my_table

|foo      |
|1        |
|2        |

As I understand, select ... for update locks all rows that are selected for that transaction. So this is what I expect:

  1. db_slow: select row 1 and lock it
  2. db_slow: see that it's only 1 row and wait
  3. db_fast: try to select row 1, see that it's locked, wait
  4. db_slow: insert row with '2'
  5. db_fast: continues because row 1 is unlocked
  6. db_fast: only selected 1 row, so it inserts '3'
  7. End up with foo: 1, 2, 3

The output and delay described above seems to confirm steps 1, 2, 3, 4. It seems like db_fast is running select after trying to obtain a lock? I thought it would select the one row, then lock or wait.


Somewhat related question:

When I run this with select ... lock in share mode I end up with

// db_slow.php
starting transaction
count tables: 1
done

// db_fast.php
starting transaction
count tables: 1
done

And my_table

|foo      |
|1        |
|3        |

Why is db_slow not inserting a row '2' even when it thinks there's only 1 row in the table (the condition to insert a row)?

도움이 되었습니까?

해결책

I think the expected behavior is a little off. Before db_slow commits, all rows in the table are locked. After it commits, there are two rows. db_fast is unblocked when db_slow commits. Hence, the behavior is:

  1. db_slow: select row 1 and lock it
  2. db_slow: see that it's only 1 row and wait
  3. db_fast: try to select row 1, see that it's locked, wait
  4. db_slow: insert row with '2'
  5. db_slow: commit
  6. db_fast: unblocked and reads 2 rows
  7. db_fast: doesn't do anything
  8. End up with foo: 1, 2
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top