Quickstart and Examples
The mysqlnd replication load balancing plugin is easy to use.
The quickstart will demo typical use-cases and give practical advice on getting
started.
It is strongly recommended to read the reference sections in addition to the
quickstart. The quickstart tries to avoid discussing theoretical concepts
and limitations. Instead, it will link to the reference sections. It is safe
to begin with the quickstart. However, before using the plugin in mission critical
environments we urge you to read additionally the background information from the
reference sections.
Setup
The plugin is implemented as a PHP extension.
Please, follow the
installation instructions to
install the
PECL/mysqlnd_ms extension.
Then, load the extension into PHP and activate the plugin in the PHP configuration
file using the PHP configuration directive named
mysqlnd_ms.enable.
The plugin is using its own configuration file. Use the PHP
configuration directive
mysqlnd_ms.ini_file
to set the full path to the plugin-specific configuration file.
This file must be readable by PHP.
Enabling the plugin (php.ini)
Create a plugin-specific configuration file. Save the file to the path
set by the PHP configuration directive
mysqlnd_ms.ini_file.
The plugin-specific file must be readable by PHP.
The plugins configuration file is divided into one or more sections.
Each section has a name, for example, myapp. Every section
makes its own set of configuration settings.
A section must at least list the MySQL replication master server.
The plugin supports using only one master server per section. Multi-master
MySQL replication setups are not supported.
Use the configuration directive
master[]
to set the hostname and the port or socket of the MySQL master server.
Minimal plugin-specific configuration file (mysqlnd_ms_plugin.ini)
It is allowed to set no MySQL slave server but it is not recommended to do.
You should always configure at least one slave server as well. Slave servers
are set using the configuration directive
slave[].
A configuration section may contain no, one or multiple
slave[] directives to set no, one or multiple
MySQL slaves.
Recommended minimal plugin-specific config (mysqlnd_ms_plugin.ini)
If there are
at least two servers in total, the plugin can start to load balance and switch
connections. Switching connections is not always transparent and can cause
issues in certain cases. The reference section on
connection pooling and switching,
transaction handling,
fail over
load balancing and
read-write splitting gives details.
For now you can continue reading the quickstart. Potential pitfalls
will be described later in the quickstart guide.
It is responsibility of the application to handle potential issues caused
by connection switches. By always configuring a master and at lease one slave
server you can be sure to detect issues early because switches become
possible.
The MySQL master and MySQL slave servers, which you configure, do not need to
be part of MySQL replication setup. For testing purpose you can use single
MySQL server and make it known to the plugin as a master and slave server
as shown below. This could help you to detect many potential issues with
connection switches. However, such a setup will not be prone to the issues
caused by replication lag.
Using one server as a master and as a slave (testing only!)Running statements
The plugin can be used with any PHP MySQL extension
(mysqli,
mysql,
PDO_MYSQL)
compiled to use the mysqlnd library.
PECL/mysqlnd_ms plugs into the mysqlnd library.
It does not change the PHP MySQL extensions and their API.
Whenever a connection to MySQL is being opened, the plugin compares the host
parameter value of the connect call with the section names
from the plugin specific configuration file. If, for example, the
plugin specific configuration file has a section myapp
the section should be referenced by opening a MySQL connection to the
host myappPlugin specific configuration file (mysqlnd_ms_plugin.ini)Opening a load balanced connection
]]>
All of three connections opened in the example will be load balanced.
The plugin will send read-only statements to the MySQL slave server with the
IP 192.168.2.27 and listening on port 3306
for MySQL client connection. All other statements will be directed to the
MySQL master server running on the host localhost accepting
MySQL client connection on the Unix domain socket /tmp/mysql.sock.
The plugin will use the user name username and the password
password to connect to any of the MySQL servers listed in
the section myapp of the plugins configuration file. Upon
connect, the plugin will select database as the current
schemata. The username, password and schema name are taken from the connect
API calls and used for all servers. In other words: you must use the same
username and password for every MySQL server listed in a plugin configuration
file section.
The plugin does not change the API for running statements.
Read-write splitting
works out of the box. The following example assumes that there is no
significant replication lag between the master and the slave.
Executing statements
query("DROP TABLE IF EXISTS test")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
if (!$mysqli->query("CREATE TABLE test(id INT)")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
if (!$mysqli->query("INSERT INTO test(id) VALUES (1)")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* read-only: statement will be run on a slave */
if (!($res = $mysqli->query("SELECT id FROM test")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
} else {
$row = $res->fetch_assoc();
$res->close();
printf("Slave returns id = '%s'\n", $row['id'];
}
$mysqli->close();
?>
]]>
&example.outputs;
The plugin does not support native prepared statements. Prepared
statements are not load balanced. Most users of
PDO_MYSQL
will be unaffected by this restriction because
PDO_MYSQL is using a
client-side prepared statement emulation by default.
Connection state
The plugin changes the semantics of a PHP MySQL connection handle.
A connection handle does no longer represent a single MySQL client-server
network connection but a connection pool. The connection pool consists
of a master connection and none, one or multiple slave connections.
Every connection from the connection pool has its own state. For example,
SQL user variables, temporary tables and transactions are part of the state.
Please, find a complete list of what belongs to the state of a connection
at the concepts page on
connection pooling and switching.
If the plugin decides to switch connections for load balancing the
application could be given connection which has a different state.
Applications must be made aware of this!
Plugin config with one slave and one masterPitfall: connection state and SQL user variables
query("SET @myrole='master'")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* Connection 2, run on slave because SELECT */
if (!($res = $mysqli->query("SELECT @myrole AS _role"))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
} else {
$row = $res->fetch_assoc();
$res->close();
printf("@myrole = '%s'\n", $row['_role']);
}
$mysqli->close();
?>
]]>
&example.outputs;
The example openes a load balanced connection and executes two statements.
The first statement SET @myrole='master' does not begin
with the string SELECT. Therefore the plugin does not
recognize it as a read-only query which shall be run on a slave. The
plugin runs the statement on the connection to the master. The statement
sets a SQL user variable which is bound to the master connection. The
state of the master connection has been changed.
The next statement is SELECT @myrole AS _role.
The plugin does recognize it as a read-only query and sends it to
the slave. The statement is run on a connection to the slave. This
second connection does not have any SQL user variables bound to it.
It has a different state than the first connection to the master.
The requested SQL user variable is not set. The example script prints
@myrole = ''.
It is the responsibility of the application developer to take care
of the connection state. The plugin does not monitor all
connection state changing activities. Monitoring all possible cases would
be a very CPU intensive task, if it could be done at all.
The pitfalls can easily be worked around using SQL hints.
SQL Hints
SQL hints can be used to force the plugin to pick a certain server from
the connection pool. Hinting the plugin to use a certain server can solve
issues caused by connection switches and connection state.
SQL hints are standard compliant SQL comments. Because
SQL comments are supposed to be ignored by SQL processing systems they
do not infere with other programs such as the MySQL Server, the MySQL Proxy
or a firewall.
Three SQL hints are supported by the plugin:
MYSQLND_MS_MASTER_SWITCH,
MYSQLND_MS_SLAVE_SWITCH and
MYSQLND_MS_LAST_USED_SWITCH.
MYSQLND_MS_MASTER_SWITCH makes the plugin run a statement
on the master, MYSQLND_MS_SLAVE_SWITCH enforces the use
of the slave and MYSQLND_MS_MASTER_SWITCH will run a
statement on the same server that has been used for running the previous
statement.
The plugin scans the beginning of a statement for the existance of a SQL
hint. SQL hints are only recognized if they appear at the very beginning of
the statement.
Plugin config with one slave and one masterSQL hints to prevent connection switches
query("SET @myrole='master'")) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* Connection 1, run on master because of SQL hint */
if (!($res = $mysqli->query(sprintf("/*%s*/SELECT @myrole AS _role", MYSQLND_MS_LAST_USED_SWITCH)))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
} else {
$row = $res->fetch_assoc();
$res->close();
printf("@myrole = '%s'\n", $row['_role']);
}
$mysqli->close();
?>
]]>
&example.outputs;
In the example the session variables issue from the previous page is solved
using MYSQLND_MS_LAST_USED_SWITCH to prevent switching
from the master to a slave when running the SELECT statement.
SQL hints can also be used to run SELECT statements
on the MySQL master server. This may be desired if the MySQL slave servers
tend to be behind the master but you need current data from the database.
Fighting replication lag
query(sprintf("/*%s*/SELECT critical_data FROM important_table", MYSQLND_MS_MASTER_SWITCH))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
?>
]]>
use case may include the creation of tables on a slave.
If no SQL hint is given, the plugin will send CREATE
and INSERT statements to the master. Use the
SQL hint MYSQLND_MS_SLAVE_SWITCH if you want to
run any such statement on a slave, for example, to build temporary
reporting tables.
Table creation on a slave
query(sprintf("/*%s*/CREATE TABLE slave_reporting(id INT)", MYSQLND_MS_SLAVE_SWITCH))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* Continue using this particular slave connection */
if (!$mysqli->query(sprintf("/*%s*/INSERT INTO slave_reporting(id) VALUES (1), (2), (3)", MYSQLND_MS_LAST_USED_SWITCH))) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* Don't use MYSQLND_MS_SLAVE_SWITCH which would allow switching to another slave! */
if ($res = $mysqli->query(sprintf("/*%s*/SELECT COUNT(*) AS _num FROM slave_reporting", MYSQLND_MS_LAST_USED_SWITCH))) {
$row = $res->fetch_assoc();
$res->close();
printf("There are %d rows in the table 'slave_reporting'", $row['_num']);
} else {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
$mysqli->close();
?>
]]>
The SQL hint MYSQLND_MS_LAST_USED forbids switching
connection and forces the use of the previously used connection.
Transactions
The current version of the plugin is not transaction safe,
because it is not transaction aware. SQL transactions are
units of work to be run on a single server.
The plugin does not know when the unit of work starts and when it ends.
Therefore, the plugin may decide to switch connections in the middle
of a transaction.
You must use SQL hints to work around this limitation.
Plugin config with one slave and one masterUsing SQL hints for transactions
query("START TRANSACTION")) {
/* Please use better error handling in your code */
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Prevent connection switch! */
if (!$mysqli->query(sprintf("/*%s*/INSERT INTO test(id) VALUES (1)", MYSQLND_MS_LAST_USED_SWITCH)))) {
/* Please do proper ROLLBACK in your code, don't just die */
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if ($res = $mysqli->query(sprintf("/*%s*/SELECT COUNT(*) AS _num FROM test", MYSQLND_MS_LAST_USED_SWITCH)))) {
$row = $res->fetch_assoc();
$res->close();
if ($row['_num'] > 1000) {
if (!$mysqli->query(sprintf("/*%s*/INSERT INTO events(task) VALUES ('cleanup')", MYSQLND_MS_LAST_USED_SWITCH)))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
}
} else {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if (!$mysqli->query(sprintf("/*%s*/UPDATE log SET last_update = NOW()", MYSQLND_MS_LAST_USED_SWITCH)))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if (!$mysqli->query(sprintf("/*%s*/COMMIT", MYSQLND_MS_LAST_USED_SWITCH)))) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
$mysqli->close();
?>
]]>
Starting with PHP 5.3.99 the mysqlnd library allows the
plugin to monitor the status of the autocommit mode, if
the mode is set by API calls instead of using SQL statements such as
SET AUTOCOMMIT=0. This makes it possible for the plugin to
become transaction aware.
If using PHP 5.3.99, API calls to set the autocommit mode
and setting the experimental plugin configuration option
trx_stickiness=master
the plugin can automatically disable load balancing and connection switches
for SQL transactions. In this configuration the plugin stops load balancing,
if autocommit is disabled and directs all statements to
the master. This is done to prevent connection switches in the middle of
a transaction. Once autocommit gets enabled again, the plugin
starts to load balance statements again.
Experimental trx_stickiness settingOutlook: transaction aware
autocommit(FALSE);
if (!$mysqli->query("INSERT INTO test(id) VALUES (1)")) {
/* Please do proper ROLLBACK in your code, don't just die */
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if ($res = $mysqli->query("SELECT COUNT(*) AS _num FROM test")) {
$row = $res->fetch_assoc();
$res->close();
if ($row['_num'] > 1000) {
if (!$mysqli->query("INSERT INTO events(task) VALUES ('cleanup')")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
}
} else {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if (!$mysqli->query("UPDATE log SET last_update = NOW()")) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
if (!$mysqli->commit()) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Plugin assumes that the transaction has ended and starts load balancing again */
$mysqli->autocommit(TRUE);
$mysqli->close();
?>
]]>
The plugin configuration option
trx_stickiness=master
is an experimental feature. It requires PHP 5.3.99.