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

Permit real prepared stored procedure #1314

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

rusher
Copy link
Contributor

@rusher rusher commented Apr 28, 2023

When using BINARY protocol, output parameters are sent using an additional result-set that is marked with intermediate EOF and ending EOF/OK Packet with flag SERVER_PS_OUT_PARAMS set (Capability PS_MULTI_RESULTS has been added to permit that).

This is an old functionality, working for any type of server > 5.0.

I think that using binary implementation must even be the basis : stored procedure can always be prepared, and only BINARY protocol permit to get output parameter.

This permits better performance than using multi-resultset with "SET @inParam1=xx;...; SELECT @outParam1,@outParam2".

Since implementation use streaming result-set, DEPRECATE_EOF capability is disabled, in order to identify "output parameter resultset" in intermediate EOF. It could be improved checking if next packet is a row packet and then fetching next packet to check if this is an EOF/OK_Packet with SERVER_PS_OUT_PARAMS flag set, but i didn't know how to make that properly.

bench base on StoredProcedureCircle test, like

using (var cmd = Connection.CreateCommand())
		{
			cmd.CommandText = "circle";
			cmd.CommandType = CommandType.StoredProcedure;
			cmd.Parameters.Add(new MySqlParameter()
			{
				ParameterName = "@radius",
				DbType = DbType.Double,
				Direction = ParameterDirection.Input,
				Value = 1.0,
			});
			...
			cmd.Parameters.Add(new MySqlParameter()
			{
				ParameterName = "@shape",
				DbType = DbType.String,
				Direction = ParameterDirection.Output,
			});
			using (var reader = await cmd.ExecuteReaderAsync())
			{
				if (await reader.ReadAsync())
					reader.GetValue(0);
			}

		}

show improved result:
with 2.3.0-beta.2:

 |                 Method |        Library |     Mean |   Error |
|----------------------- |--------------- |---------:|--------:|
|     storeProcedureText | MySqlConnector | 120.9 us | 2.41 us |

with PR:

|                 Method |        Library |      Mean |    Error |
|----------------------- |--------------- |----------:|---------:|
|     storeProcedureText | MySqlConnector | 118.79 us | 2.311 us |
| storeProcedurePrepared | MySqlConnector |  93.31 us | 1.021 us |

When using BINARY protocol, output parameters are sent using an additional result-set that is marked with intermediate EOF and ending EOF/OK Packet with flag [SERVER_PS_OUT_PARAMS](https://mariadb.com/kb/en/ok_packet/#server-status-flag) set (Capability PS_MULTI_RESULTS has been added to permit that).

This is an old functionality, working for any type of server > 5.0.

I'm even wondering if using binary cannot be used in all situations, because stored procedure can always be prepared, and only BINARY protocol permit to get output parameter.

This permits better performance than using multi-resultset with "SET @inParam1=xx;...; SELECT @outParam1,@outParam2".

Since implementation use streaming result-set, DEPRECATE_EOF capability is disabled, in order to identify "output parameter resultset" in intermediate EOF. It could be improved checking if next packet is a row packet and then fetching next packet to check if this is an EOF/OK_Packet with SERVER_PS_OUT_PARAMS flag set, but i didn't know how to make that properly.

 bench base on StoredProcedureCircle test, like
 ```
 using (var cmd = Connection.CreateCommand())
 		{
 			cmd.CommandText = "circle";
 			cmd.CommandType = CommandType.StoredProcedure;
 			cmd.Parameters.Add(new MySqlParameter()
 			{
 				ParameterName = "@radius",
 				DbType = DbType.Double,
 				Direction = ParameterDirection.Input,
 				Value = 1.0,
 			});
 			...
 			cmd.Parameters.Add(new MySqlParameter()
 			{
 				ParameterName = "@shape",
 				DbType = DbType.String,
 				Direction = ParameterDirection.Output,
 			});
 			using (var reader = await cmd.ExecuteReaderAsync())
 			{
 				if (await reader.ReadAsync())
 					reader.GetValue(0);
 			}

 		}
 ```
 show improved result:
 with 2.3.0-beta.2:
 ```
  |                 Method |        Library |     Mean |   Error |
 |----------------------- |--------------- |---------:|--------:|
 |     storeProcedureText | MySqlConnector | 120.9 us | 2.41 us |
 ```

 with PR:
 ```
 |                 Method |        Library |      Mean |    Error |
 |----------------------- |--------------- |----------:|---------:|
 |     storeProcedureText | MySqlConnector | 118.79 us | 2.311 us |
 | storeProcedurePrepared | MySqlConnector |  93.31 us | 1.021 us |
```

Signed-off-by: rusher <diego.dupin@gmail.com>
Comment on lines -8 to +9
private static ByteBufferWriter CreateCapabilitiesPayload(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, bool useCompression, CharacterSet characterSet, ProtocolCapabilities additionalCapabilities = 0)
{
var writer = new ByteBufferWriter();

var clientCapabilities = (ProtocolCapabilities.Protocol41 |
public static ProtocolCapabilities CreateClientCapabilities(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, bool useCompression, ProtocolCapabilities additionalCapabilities = 0) =>
(ProtocolCapabilities.Protocol41 |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't tell if this was a required change, or just a refactoring done along the way?

Either way, some change here seems to be causing a "Bad handshake" error.

m_useCompression = cs.UseCompression && (initialHandshake.ProtocolCapabilities & ProtocolCapabilities.Compress) != 0;
clientCapabilities =
HandshakeResponse41Payload.CreateClientCapabilities(initialHandshake.ProtocolCapabilities, cs, m_useCompression,
ProtocolCapabilities.Ssl);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure that it's correct to advertise ProtocolCapabilities.Ssl here if a SSL connection may not be attempted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this is a bug

Comment on lines -29 to +28
ProtocolCapabilities.DeprecateEof |

// ProtocolCapabilities.DeprecateEof |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure that this tradeoff is worth the benefit; the EOF packet has been deprecated since MySQL 5.7.5, and this enables CLIENT_SESSION_TRACK, etc.

@@ -12,6 +12,8 @@ namespace MySqlConnector;

public sealed class MySqlParameter : DbParameter, IDbDataParameter, ICloneable
{
public static readonly MySqlParameter NullParameter = new MySqlParameter("", null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid creating a public static property with a value that's a mutable object. User code could run MySqlParameter.NullParameter.Value = 42.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct, i'll correct that

cmd.Parameters.Add(new() { Value = schema });
cmd.Parameters.Add(new() { Value = component });
cmd.Parameters.Add(new() { Value = schema });
cmd.Parameters.Add(new() { Value = component });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the changes in this file required for this PR, or just an optimisation (to reuse this command server-side) that seemed desirable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think it should be unnecessary to switch from named to positional parameters (and double the parameter count), just because you're calling Prepare. The parameter binding code should figure that out IIRC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might actually be a "pessimisation"; since there are two statements, two COM_EXECUTE_STMT payloads will have to be sent (instead of one COM_QUERY), so there will be multiple DB roundtrips. A quick benchmark bears that out:

Method Mean Error StdDev Allocated
Normal 3.190 ms 0.0975 ms 0.2766 ms 3.43 KB
Prepared 3.983 ms 0.0783 ms 0.1490 ms 3.08 KB

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change can really be changed into just "cmd.Prepare();".
About "pessimisation", this isn't exact: the first stored procedure will indeed have a PREPARE + EXECUTE command that will be slower that a COM_QUERY command, but each time another stored procedure command will be called, only an EXECUTE command will be issued, and this will be faster (especially when using mariadb now that metadata skip is implemented). I hesitate to improve PREPARE + EXECUTE in the same PR, but this will be in another PR : MariaDB permit pipelining PREPARE + EXECUTE command, EXECUTE using statement id 0xffffffff meaning using last prepared statement, this permits to have nearly the same execution time than a COM_QUERY, but next execution being just an EXECUTE command will then be way better.

Copy link
Member

@bgrainger bgrainger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this PR mixes multiple refactorings (CachedProcedure, HandshakeResponse41Payload) with the actual functional change being made. Please limit the PR to the minimum code necessary to accomplish the change, and open additional PRs for the other changes. (Or structure it as a clear sequence of independent commits that can be reviewed independently.)

While you noted that this functionality is "working for any type of server > 5.0", I wonder if this code should be gated by a test for ProtocolCapabilities.PreparedStatementMultiResults to help compatibility with non-MySQL servers that speak the MySQL protocol?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants