简体   繁体   中英

Understanding pdo mysql transactions

The PHP Documentation says:

If you've never encountered transactions before, they offer 4 major features: Atomicity, Consistency, Isolation and Durability (ACID). In layman's terms, any work carried out in a transaction, even if it is carried out in stages, is guaranteed to be applied to the database safely, and without interference from other connections, when it is committed.

QUESTION:

Does this mean that I can have two separate php scripts running transactions simultaneously without them interfering with one another?


ELABORATING ON WHAT I MEAN BY " INTERFERING " :

Imagine we have the following employees table:

 __________________________
|  id  |  name  |  salary  |
|------+--------+----------|
|  1   |  ana   |   10000  |
|------+--------+----------|

If I have two scripts with similar/same code and they run at the exact same time:

script1.php and script2.php (both have the same code):

$conn->beginTransaction();

$stmt = $conn->prepare("SELECT * FROM employees WHERE name = ?");
$stmt->execute(['ana']);
$row = $stmt->fetch(PDO::FETCH_ASSOC);

$salary = $row['salary'];
$salary = $salary + 1000;//increasing salary

$stmt = $conn->prepare("UPDATE employees SET salary = {$salary} WHERE name = ?");
$stmt->execute(['ana']);

$conn->commit(); 

and assuming the sequence of events is as follows:

  • script1.php selects data

  • script2.php selects data

  • script1.php updates data

  • script2.php updates data

  • script1.php commit() happens

  • script2.php commit() happens

What would the resulting salary of ana be in this case?

  • Would it be 11000? And would this then mean that 1 transaction will overlap the other because the information was obtained before either commit happened?

  • Would it be 12000? And would this then mean that regardless of the order in which data was updated and selected, the commit() function forced these to happen individually?

Please feel free to elaborate as much as you want on how transactions and separate scripts can interfere (or don't interfere) with one another.

You are not going to find the answer in php documentation because this has nothing to do with php or pdo.

Innodb table engine in mysql offers 4 so-called isolation levels in line with the sql standard. The isolation levels in conjunction with blocking / non-blocking reads will determine the result of the above example. You need to understand the implications of the various isolation levels and choose the appropriate one for your needs.

To sum up: if you use serialisable isolation level with autocommit turned off, then the result will be 12000. In all other isolation levels and serialisable with autocommit enabled the result will be 11000. If you start using locking reads, then the result could be 12000 under all isolation levels.

Judging by the given conditions (a solitary DML statement), you don't need a transaction here, but a table lock. It's a very common confusion.

You need a transaction if you need to make sure that ALL your DML statements were performed correctly or weren't performed at all.

Means

  • you don't need a transaction for any number of SELECT queries
  • you don't need a transaction if only one DML statement is performed

Although, as it was noted in the excellent answer from Shadow, you may use a transaction here with appropriate isolation level, it would be rather confusing. What you need here is table locking . InnoDB engine lets you lock particular rows instead of locking the entire table and thus should be preferred.

In case you want the salary to be 1200 - then use table locks.

Or - a simpler way - just run an atomic update query:

UPDATE employees SET salary = salary + 1000 WHERE name = ?

In this case all salaries will be recorded.

If your goal is different, better express it explicitly.

But again: you have to understand that transactions in general has nothing to do with separate scripts execution. Regarding your topic of race condition you are interested not in transactions but in table/row locking. This is a very common confusion, and you better learn it straight:

  • a transaction is to ensure that a set of DML queries within one script were executed successfully.
  • table/row locking is to ensure that other script executions won't interfere.

The only topic where transactions and locking interfere is a deadlock , but again - it's only in case when a transaction is using locking.

Alas, the "without interference" needs some help from the programmer. It needs BEGIN and COMMIT to define the extent of the 'transaction'. And...

Your example is inadequate. The first statement needs SELECT ... FOR UPDATE . This tells the transaction processing that there is likely to be an UPDATE coming for the row(s) that the SELECT fetches. That warning is critical to "preventing interference". Now the timeline reads:

  • script1.php BEGINs
  • script2.php BEGINs
  • script1.php selects data ( FOR UPDATE )
  • script2.php selects data is blocked, so it waits
  • script1.php updates data
  • script1.php commit() happens
  • script2.php selects data (and will get the newly-committed value)
  • script2.php updates data
  • script2.php commit() happens

(Note: This is not a 'deadlock', just a 'wait'.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM