-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Feature: [pdo_firebird] Transaction management optimization #12741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: [pdo_firebird] Transaction management optimization #12741
Conversation
ad40a7b
to
5df9266
Compare
I forgot to use constants in a few places, so I'll fix them. |
5df9266
to
0765b34
Compare
done. |
When working with multiple instances, I found that keeping a transaction always started can lead to non-intuitive behavior depending on the isolation level. I'll think about it some more. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with the tests changes to remove the commit()
method calls.
However, the C code is rather confusing, I'm struggling to see if functions prefixed by firebird_
are from the PHP driver or actual FireBird/Interbase APIs.
I think it would be better first, maybe in a different PR to rename the PHP driver related function to use the more typical php_
prefix. So the PHP driver functions would be prefixed by php_firebird_driver_
would hopefully make it easier to understand what is going on.
Also, I'm not a fan of a leading _
to mark the function "private" if it is static
and in a .c
file then it is private and limited to that file anyway.
ext/pdo_firebird/firebird_driver.c
Outdated
{ | ||
pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; | ||
|
||
if (isc_commit_transaction(H->isc_status, &H->tr)) { | ||
firebird_error(dbh); | ||
if (dbh->auto_commit && H->tr && !firebird_commit_transaction(dbh, FB_TXN_RELEASE)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this firebird_commit_transaction()
function part of the FireBird API or the PHP Driver?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's a replacement of _firebird_begin_transaction
with an h file. It's quite difficult to understand....
As you pointed out, I should think about the naming a little more.
extern bool _firebird_commit_transaction(pdo_dbh_t *dbh, bool retain); | ||
#define firebird_commit_transaction(d,r) _firebird_commit_transaction(d, r) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extern bool _firebird_commit_transaction(pdo_dbh_t *dbh, bool retain); | |
#define firebird_commit_transaction(d,r) _firebird_commit_transaction(d, r) | |
extern bool firebird_commit_transaction(pdo_dbh_t *dbh, bool retain); |
Why not just this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I misunderstood what the naming convention should be and it became complicated.
ext/pdo_firebird/tests/ddl2.phpt
Outdated
$result = $dbh->query("SELECT * FROM test_ddl2"); | ||
foreach ($result as $r) { | ||
var_dump($r); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$result = $dbh->query("SELECT * FROM test_ddl2"); | |
foreach ($result as $r) { | |
var_dump($r); | |
} | |
$result = $dbh->query("SELECT * FROM test_ddl2"); | |
var_dump($result); |
As this is an empty array just having this be the expectation is clearer IMHO
Looking back on it, it seems quite difficult to understand... |
There are other things that I'm confused about. Firebird's transaction isolation level is repeatable read by default, so as it stands now, when in autocommit mode, it cannot read any changes committed from other transactions since To address this, I came up with three ideas:
Personally, I think 2 has the lowest communication cost, but if you don't mind, could you give me your opinion? |
I'm not totally sure about what you mean, but shouldn't the |
In that case, the transaction will remain closed and it will have to begin again at some point.(In autocommit mode) I think it's probably easier to understand if I explain the reproduction case using php code, so please wait for a moment. |
I put together some patterns Cases where the current implementation causes problems<?php
$db = new PDO(/**/);
$db_other = new PDO(/**/);
$db_other->exec('CREATE TABLE test (val INT)');
$db->query('SELECT * FROM test'); output:
Replace `isc_commit_retaining` with `isc_commit_transaction`replace( in
<?php
$db = new PDO(/**/);
$db_other = new PDO(/**/);
$db_other->exec('CREATE TABLE test (val INT)');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$db->query('SELECT * FROM test');
var_dump($db->query('SELECT * FROM test')); output:
When the isolation level is set to read committed<?php
$db = new PDO(/**/);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$db->setAttribute(PDO::FB_TRANSACTION_ISOLATION_LEVEL, PDO::FB_READ_COMMITTED); // Implemented for verification
$db_other = new PDO(/**/);
$db_other->exec('CREATE TABLE test (val INT)');
$db_other->exec('INSERT INTO test VALUES (1)');
$r = $db->query('SELECT * FROM test');
foreach ($r as $row) {
var_dump($row);
} output:
|
Right I see the issue, apparently the transaction isolation levels are an ANSI/ISO SQL spec, so those should possibly be added to PDO as a whole and not just limited to PDO_FireBird. Also https://github.com/FirebirdSQL/jaybird-manual/blob/master/src/docs/asciidoc/chapters/transactions/transactions.adoc#auto-commit-mode was useful for me to understand some of the issues. (also I will note I am not a database expert and this is new to me) |
Thank you for taking a closer look!
For DBs other than firebird, transaction isolation levels can be set on a per-session basis or in global settings. example MySQL:
https://dev.mysql.com/doc/refman/8.0/en/set-transaction.html Therefore, non-Firebird DBs can manipulate transaction isolation levels by executing instructions directly in SQL. Firebird cannot set the transaction isolation level "only" like MySQL. Firebird specifies the isolation level when starting a transaction. If omitted, it becomes the default value, but the default value cannot be changed (as far as I know). This is a change that I plan to next this pull request, but for these reasons, in the case of firebird, it is necessary to maintain the transaction isolation level setting value on the php side. So I think that the attribute value will be for only firebird. |
Done! |
89b6b3d
to
5d69e85
Compare
#define FB_TXN_RETAIN 1 | ||
#define FB_TXN_RELEASE 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly make this a C enum?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, I changed it.
095188a
to
0bc6851
Compare
I rebased on master |
ext/pdo_firebird/firebird_driver.c
Outdated
if (dbh->auto_commit && H->tr && !php_firebird_commit_transaction(dbh, FB_TXN_RELEASE)) { | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this if block checks for.
If I understand properly, does this check that the current "active" FireBird transcation is empty? Anycase having comments would be helpful
enum { | ||
FB_TXN_RELEASE, | ||
FB_TXN_RETAIN | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requires a typedef and this type to be passed to php_firebird_commit_transaction
instead of bool
, or am I misunderstanding something here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it would be difficult to understand if it was just a bool value, so I made it a constant.
php_firebird_commit_transaction(dbh, true) // retain
php_firebird_commit_transaction(dbh, false) // not retain
Maybe I'm overthinking it a bit, and maybe just passing a bool is fine...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try to stop using constants (Enums).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can write:
php_firebird_commit_transaction(dbh, /* retain */ retain_var_or_const);
To imitate named arguments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good idea! Thank you!
ext/pdo_firebird/firebird_driver.c
Outdated
{ | ||
pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; | ||
|
||
if (isc_commit_transaction(H->isc_status, &H->tr)) { | ||
php_firebird_error(dbh); | ||
if (dbh->auto_commit && H->tr && !php_firebird_commit_transaction(dbh, FB_TXN_RELEASE)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (dbh->auto_commit && H->tr && !php_firebird_commit_transaction(dbh, FB_TXN_RELEASE)) { | |
php_firebird_commit_transaction(dbh, FB_TXN_RELEASE) |
I'm confused, FB_TXN_RELEASE
is it's own enum and not a bool, so this is slightly confusing
ext/pdo_firebird/firebird_driver.c
Outdated
/* php_firebird_commit_transaction */ | ||
bool php_firebird_commit_transaction(pdo_dbh_t *dbh, bool retain) /* {{{ */ | ||
{ | ||
pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; | ||
|
||
if (retain) { | ||
if (isc_commit_retaining(H->isc_status, &H->tr)) { | ||
php_firebird_error(dbh); | ||
return false; | ||
} | ||
} else { | ||
if (isc_commit_transaction(H->isc_status, &H->tr)) { | ||
php_firebird_error(dbh); | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
/* }}} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add comments to explain why the two different behaviours are needed?
0bc6851
to
4764236
Compare
Added some comments. Also, if we try to change the autocommit mode while a transaction has been manually started, an error will now occur. |
4764236
to
751c796
Compare
ext/pdo_firebird/firebird_driver.c
Outdated
if (dbh->auto_commit && H->tr) { | ||
if (!php_firebird_commit_transaction(dbh, /* release */ false)) { | ||
return false; | ||
} | ||
} | ||
|
||
if (!php_firebird_begin_transaction(dbh)) { | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I don't set this to retain is because I want to change the isolation level when I introduce it in the next PR.
After thinking about it, I shouldn't be retaining when returning from a manual transaction to autocommit, so I'll fix that. Along with that, there is no place to use retain for rollback, so delete it. |
done |
ec2f6a4
to
278593f
Compare
added some tests |
16962f7
to
7d907fc
Compare
7d907fc
to
c6881cb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks sensible to me now, thanks for tackling this and all the explanations
Thank you! |
take2 of #12657
About Firebird transaction
Firebird is a full transactional database, so the DB itself does not support autocommit mode. (Strictly, there is an autocommit mode, but it is a different concept from the "autocommit" that we are used to with MySQL and others.)
Therefore, a transaction must have started before any operation is performed, and autocommit mode had to be emulated in php.
I made sure that a transaction always exists when in autocommit mode. Since the
in_transacntion
function does not work as expected, I have introducedH->in_manually_txn
to determine whether a transaction is being manually manipulated.There are two types of commit/rollback
(I'm not talking about two-phase commit. This change does not take into account two-phase commit.)
There are
isc_commit_retaining
which starts a transaction again in the same context immediately after committing, andisc_commit_transaction
which closes the transaction as is.Similarly, there are two types of rollback.
Due to the default value of the transaction isolation level, autocommit mode may obtain unintended results.
Regarding this, it would be too large to include support for transaction isolation levels in this PR, so I will leave it as is for now. (I'll address it with the following changes, I'll probably create a PR soon)