Hi. I would like to offer three changes to PDO module.
- Cleanup persistent connection before passing it to next request
Documentation on persistent connections [1] says:
Warning
PDO does not perform any cleanup of persistent connections. Temporary
tables, locks, transactions and other stateful changes may remain from
previous usage of the connection, causing unexpected problems.
This makes persistent connections effectively unusable. I don't want to
obtain dirty connection from previous request. In contrast, mysqli
extension do automatic cleanup [2]:
The persistent connection of the mysqli extension however provides
built-in cleanup handling code.
Adding cleanup code to PDO will align persisting connection behavior with
mysqli and make persistent connections safe to use. This is breaking change.
- Add new method to PDO class -
reset()
This method will reset connection state. For mysql it will call C-API
function mysql_change_user() like mysqli do. Most value this method would
bring to event loop environments (more on this below), but also can be non
breaking alternative to my first proposal - I just call this method on the
very beginning of my script and proceed without fear, that everything may
break due to garbage state from previous worker request.
After calling proposed refresh() method: all still alive PDOStatement
invalidated, logical db connection refreshed (e.g. mysql_change_user C-API
called), underlying TCP/unix-socket/file-pointer connection stay intact.
- Add new method to PDO class -
close()
PDO does not have method for closing connection. Documentation [1] says:
To close the connection, you need to destroy the object by ... assigning
null to the variable that holds the object.
Explicit close is not necessary for fpm and mod_php environments, because
connection automatically closed at the end of script execution. But in
event loops this may be complicated because of internal references to PDO
object from PDOStatement objects [1]:
Note: If there are still other references to this PDO instance (such as
from a PDOStatement instance, or from other variables referencing the same
PDO instance), these have to be removed also (for instance, by assigning
null to the variable that references the PDOStatement).
After calling proposed close() method: all still alive PDOStatement
objects invalidated, logical db connection closed (e.g. mysql_close C-API
called), underlyind TCP/unix-socket/file-pointer connection closed.
Event loop considerations
Event loop based applications (e.g. roadrunner, frankenphp) are main
intended users of reset() and close() methods. Event loop may look like:
// Example pseudocode
$ev = setupEventLoopEnvironment();
$app = setupApplication();
while ($request = $ev->waitForRequest()) {
$db = new PDO('dsn', 'user', 'password');
$app->setDb($db);
$app->handle($request);
$db = null;
$app->setDb(null);
}
Such approach may lead to db connection leak because application may
unintentionally cache PDO or PDOStatement object that would prevent
connection from closing. However, this may be handled by hiding PDO objects
behind abstraction, provided by event loop framework. But performance
impact cannot be avoided.
More safe and performant variant using reset():
$ev = setupEventLoopEnvironment();
$db = new PDO('dsn', 'user', 'password');
$app = setupApplication();
$app->setDb($db);
while ($request = $ev->waitForRequest()) {
$db->reset();
$app->handle($request);
}
On this mail I focused on mysql. If the community is interested by the
proposed changes, I'll investigate other supported db drivers as well and
write RFC.
[1] https://www.php.net/manual/en/pdo.connections.php
[2] https://www.php.net/manual/en/mysqli.persistconns.php
Hi,
- Automatic cleanup is something that we could add to PDO, at least
for MySQL. I am not sure if other drivers have reset methods. The
cleanup should probably happen when requesting the connection again.
But there is a reason why mysqli allows for this feature to be
switched off in the compilation options. Certain users do not like
this. It is silently discarding errors and data, which could be
disastrous in some situations. If such a feature is added to PDO, it
should be opt-in, so that users aren't automatically enrolled in a
dangerous feature.
- A reset method sounds really reasonable, even if it is only a
driver-specific method for MySQL. It should probably use
mysql_reset_connection(). If this method is added, then we don't need
to bother adding automatic cleanup, as this would be a far better
option.
PDO does not have method for closing connection.
As I said in an earlier email, this is intentional. Such a method is a bad idea.
But in event loops this may be complicated because of internal references to PDO object from PDOStatement objects
And that's very good. As long as any PDOStatement or PDO objects refer
to the connection, it should NOT be closed. Giving users such an
option would only result in creating crippled objects for no apparent
benefit. I stand by what I said before: in a well-designed
application, there is absolutely no reason to close the connection
explicitly.
Regards,
Kamil
But there is a reason why mysqli allows for this feature to be
switched off in the compilation options. Certain users do not like
this.
I'm pretty sure this option introduced not by user demand. But
because maintainers thought it may be useful.
This should be opt in. Because backward compatibility, not
because someone disliked it. Can't imagine situations where
obtaining dirty connect from previous script execution would be
useful.
This may be implemented using pdo option:
$db = new PDO(
'dsn', 'user', 'password', [
PDO::ATTR_PERSISTENT => true
PDO::ATTR_AUTO_RESET_PERSISTENT => true,
]
);
It is silently discarding errors and data, which could be
disastrous in some situations.
That's exactly what I want - not bothering about errors and
data left from previous script.
PDO does not have method for closing connection.
As I said in an earlier email, this is intentional. Such a method is
a bad idea.
We have mysqli::close and this is ok. But PDO::close is a bad idea?
I don't understand.
I stand by what I said before: in a well-designed
application, there is absolutely no reason to close the connection
explicitly.
Creating well-designed application is hard. As a creator of event loop
framework I can't be sure that every user of my framework is a superstar
in programming and application design. So I'd prefer to leave my users
with "crippled objects" that would fail-fast in the next loop iteration
instead of invisible db connection leak.
This is also very suitable for True Async RFC [1]. In that case if you
want send queries in parallel coroutines, then db connection pool should
be used. And for connection pool implementations it's mandatory to have
possibility to close db connection explicilty.
Hello,
Almost all of these changes relate to the connection pool
implementation. For it to work correctly, the language must support a
concurrent mode of operation with the EventLoop.
There’s no way to implement this in isolation from the overall architecture.
Mysqli already have all three features mentioned here
and works fine without concurrent mode of operation.