sidorares / node-mysql2

:zap: fast mysqljs/mysql compatible mysql driver for node.js
https://sidorares.github.io/node-mysql2/
MIT License
4.1k stars 625 forks source link

What happens when a connection is released? #1087

Open joeyhub opened 4 years ago

joeyhub commented 4 years ago

As far as I can understand a pool connection when released will at least make it available to any waiting connection. This means it's a persistent connection.

Does this perform any kind of cleanup? What happens if it is released with a transaction still in play?

joeyhub commented 4 years ago

Is it possible to have a pool that instead keeps connections open in advance rather than reusing?

sidorares commented 4 years ago

this is the code for releaseConnection - https://github.com/sidorares/node-mysql2/blob/2824df36a3fb7113ae44320871ae574ca56f61a7/lib/pool.js#L79-L94

In short, it's just returned to list of "free" connection and is up for grabs for next .getConnection call. You are responsible for cleaning up connection state.

joeyhub commented 4 years ago

If I destroy the connection instead such as using end does the pool know to create a new connection?

For transactional workloads first and foremost I usually have the connection die on any error at all with the default for a lost connection always being rollback. I tend to assume the worst rather than risk committing a partially complete transaction.

This also applies to connection persistence in those cases. Unless a clean state can be guaranteed I wouldn't use it. I already saw at that code and honestly had to ask because it's extremely unsafe that I couldn't believe my eyes and hoped something else was happening out of sight.

MySQL transactions offer various guarantees and I feel like the documentation where possible should be very clear about whether or not it maintains those.

The simplest way to be able to maintain them is if rather than release the connection can be ended and the pool will make a new one. I have a feeling there may be a command or will be to reset the current connection but I'm not sure.

I would not recommend implementing an attempt to clean connections by hand because it gets very complicated very quickly with transaction state, variable state, temp table state, etc.

I believe traditionally this is meant for reusing connections safely in a pool:

https://dev.mysql.com/doc/refman/8.0/en/mysql-reset-connection.html

joeyhub commented 4 years ago

https://github.com/mysql/mysql-server/blob/91a17cedb1ee880fe7915fb14cfd74c04e8d6588/libmysql/libmysql.cc

joeyhub commented 4 years ago

In the mean time a work around is crucial or use of pool is a no-no for guarantee based dev unless DIY.

I think ensuring and documenting end is fine to use instead of release for safety is crucial.

Aside from that full transaction support is really complex. I've never seen an application library do it properly.

Start transaction has to take read only, write, isolation, etc then for nested transactions you have to check that, return a transaction handler (people use counters but it doesn't detect a missed call to commit, etc) and then normally nested transactions simply skip starting the transaction as already, commit is easily ignored and you go down the stack but rollback raises questions. Then you have savepoints, chain, etc. Gets more complicated if abstracted to the point start transaction returns a new exclusive connection, or not if only user (but not possible here on pool level with no ref counting if command is directly on pool so would always be new connection).

I'm sceptical that it's ever been done.

Though getting the basics would at least allow people to reliable add what they need on top.

sidorares commented 4 years ago

changeUser() with no parameters resets connection state

https://github.com/mysqljs/mysql#switching-users-and-altering-connection-state

The problem I see here is that it adds big performance penalty - chageUser command itsef is one extra network trip, plus prepares statements are invalidated, plus query cache etc etc. Maybe we should have pool config parameter "reset connection state after reuse", is set to true connection automatically gets changeUser command before release

similar issue: #934

joeyhub commented 4 years ago

This one can be a bit opinionated sometimes but is important. People tend to operate MySQL with two philosophies, performance first or data safety first and usually they're mixed up.

I'm going for safety first so replacing release with end connection for exclusive connections and hopefully that'll work.

end() { const err = new Error( 'Calling conn.end() to release a pooled connection is ' + 'deprecated. In next version calling conn.end() will be ' + 'restored to default conn.end() behavior. Use ' + 'conn.release() instead.' ); this.emit('warn', err); // eslint-disable-next-line no-console console.warn(err.message); this.release(); }

Almost went down the wrong path. It seems destroy is the only canonical way and it's not async though worser things have happened. At a quick glance it looks as though the pool should refill the connection?

Down the line being able to reset the session instead as a minor performance boost over a full reconnect is a nice to have with safety first being important.

Although changeUser appears to be public it doesn't look like something the pool is aware off. It would technically work though also having an explicit reset connection command would be more concise.

Really the pool connection and pool would in an ideal world have a better state tracking and matching mechanism. I think it would be very little different to this:

https://github.com/sidorares/node-mysql2/blob/a0ea513b0d4929e6ee61639a09f38a85ac01f7fa/lib/commands/ping.js

joeyhub commented 4 years ago

https://github.com/mysqljs/mysql/issues/780

It was stuck open here too.

joeyhub commented 4 years ago

For now I'm trying with making a custom reset command (same as ping but 0x1f) then going to try addCommand on the connection but it leaves a lot to be desired such as if an error in logic somehow causes a problem while they're still a command in waiting it's unclear if the connection will hang or not or worse.

It gets called as part of my own connection wrapper's destroy which is supposed to always be called along with my own half baked GC conventions.

sidorares commented 4 years ago

https://dev.mysql.com/doc/internals/en/com-reset-connection.html

I definitely want to see .reset() implemented here ( and maybe help mysqljs/mysql implement it ) Few things in addition just having reset() { this.addCommand(new Commands.Reset() }:

sidorares commented 4 years ago

also need to add command code to https://github.com/sidorares/node-mysql2/blob/master/lib/constants/commands.js

tfrancois commented 3 years ago

I think it's also worth mentioning the functionality requested here can also be related with: https://github.com/sidorares/node-mysql2/issues/586#issuecomment-307376690

EffectivelyEfficient commented 1 year ago

Use cases

Issues

Recommended actions