Skip to content
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

OCI8 transactions are not isolated #4417

Open
BenMorel opened this issue Nov 10, 2020 · 8 comments
Open

OCI8 transactions are not isolated #4417

BenMorel opened this issue Nov 10, 2020 · 8 comments

Comments

@BenMorel
Copy link
Contributor

BenMorel commented Nov 10, 2020

Bug Report

Q A
BC Break no
Version 2.12.0

While creating a failing test case for LockMode::NONE on SQL Server & SQL Anywhere in #4415, I noticed that OCI8 fails as well for an unknown reason:

https://github.com/doctrine/dbal/pull/4415/checks?check_run_id=1371509666

The behaviour is the following, given two connections, C1 and C2:

C1: CREATE TABLE users (id NUMBER(10) NOT NULL)
C1: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
C2: SET TRANSACTION ISOLATION LEVEL READ COMMITTED
C1: "START TRANSACTION"
C2: "START TRANSACTION"
C1: INSERT INTO users (id) VALUES (1)
C2: SELECT id FROM users ⚠️ returns the record inserted by C1! ⚠️
C1: "COMMIT"
C2: "COMMIT"
C1: DROP TABLE users

It looks the test works fine on PDO_OCI, so this may be a bug with OCI8.

Possible causes I can think of:

  • Transactions are not actually started?
  • Wrong isolation level set?
  • TestUtil::getConnection() returns the same connection twice?
@morozov
Copy link
Member

morozov commented Nov 14, 2020

I think this is by design from the OCI standpoint but Doctrine doesn't support this use case. From the documentation:

If two handles need to be transactionally isolated from each other, use oci_new_connect() instead.

@morozov
Copy link
Member

morozov commented Nov 14, 2020

diff --git a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php
index 8e043ff54..d64b73dc2 100644
--- a/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php
+++ b/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php
@@ -62,7 +62,7 @@ class OCI8Connection implements ConnectionInterface, ServerInfoAwareConnection
     ) {
         $dbh = $persistent
             ? @oci_pconnect($username, $password, $db, $charset, $sessionMode)
-            : @oci_connect($username, $password, $db, $charset, $sessionMode);
+            : @oci_new_connect($username, $password, $db, $charset, $sessionMode);

         if ($dbh === false) {
             throw OCI8Exception::fromErrorInfo(oci_error());
diff --git a/tests/Doctrine/Tests/DBAL/Functional/LockMode/NoneTest.php b/tests/Doctrine/Tests/DBAL/Functional/LockMode/NoneTest.php
index e91dd2a82..39e5cf385 100644
--- a/tests/Doctrine/Tests/DBAL/Functional/LockMode/NoneTest.php
+++ b/tests/Doctrine/Tests/DBAL/Functional/LockMode/NoneTest.php
@@ -24,11 +24,6 @@ class NoneTest extends DbalFunctionalTestCase
     {
         parent::setUp();

-        if ($this->connection->getDriver() instanceof OCI8\Driver) {
-            // https://github.com/doctrine/dbal/issues/4417
-            self::markTestSkipped('This test fails on OCI8 for a currently unknown reason');
-        }
-
         if ($this->connection->getDatabasePlatform() instanceof SQLServerPlatform) {
             // Use row versioning instead of locking on SQL Server (if we don't, the second connection will block when
             // attempting to read the row created by the first connection, instead of reading the previous version);
$ phpunit -c oci8.phpunit.xml tests/Doctrine/Tests/DBAL/Functional/LockMode/NoneTest.php
PHPUnit 9.4.0 by Sebastian Bergmann and contributors.

.                                                                                   1 / 1 (100%)

Time: 00:00.854, Memory: 16.00 MB

OK (1 test, 1 assertion)

@BenMorel
Copy link
Contributor Author

Thanks, @morozov. Should oci_new_connect() be used by default instead, then?

@morozov
Copy link
Member

morozov commented Nov 14, 2020

Should oci_new_connect() be used by default instead, then?

I would think twice. It may cause a performance impact in terms of establishing a DB connection and, more importantly, using prepared statements.

I'm open to adding another connection flag that would use oci_new_connect() internally. But in order to make it the default, we need to do some performance testing.

@BenMorel
Copy link
Contributor Author

TBH, I'd rather make that the default and add an option to use oci_connect() instead. So that by default, all connections created by the DBAL are transactionally isolated, which is the case if I'm not mistaken, for all other drivers.

@morozov
Copy link
Member

morozov commented Nov 14, 2020

It can be done in the next major since it's a breaking change.

@beberlei
Copy link
Member

beberlei commented Dec 7, 2020

It could be done with an opt-in flag passed as $params to switch from the old to the new.

But I don't think this causes performance problems as it would require someone to use two Doctrine Connection objects or connect with exactly the same params outside of Doctrine, where not having isolation from might even be considered dangerous from Doctrine POV.

@morozov
Copy link
Member

morozov commented Dec 7, 2020

It could be done with an opt-in flag passed as $params to switch from the old to the new.

Yes, my BC argument was about changing the default.

But I don't think this causes performance problems as it would require someone to use two Doctrine Connection objects or connect with exactly the same params outside of Doctrine

Not sure how this is relevant to Doctrine. AFAIK, Oracle Instant Client maintains the connections within itself. Hence, the same connection with the same credentials can be reused including all the prepared statements, etc. If they cannot be reused, it may have its side effects.

I believe introducing a flag now and changing the default later is fine.

@github-actions github-actions bot added the Stale label Nov 1, 2021
@morozov morozov removed the Stale label Nov 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants