Quickstart and Examples
The mysqlnd replication load balancing plugin is easy to use.
This quickstart will demo typical use-cases, and provide 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.
The documentation has been updated to show the syntax used as of version
1.1.0-beta. PECL/mysqlnd_ms 1.1.0-beta introduces many
changes. Among
others, it is using a new JSON
based
plugin configuration file
format.
Setup
The plugin is implemented as a PHP extension. See also 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 uses its own configuration file. Use the PHP
configuration directive
mysqlnd_ms.ini_file
to set the full file path to the plugin-specific configuration file.
This file must be readable by PHP (e.g., the web server user).
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 plugins configuration file
is JSON based. It 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 a minimum, list the MySQL replication master server, and set
a list of slaves. The plugin supports using only one master server per section.
Multi-master MySQL replication setups are not yet fully supported.
Use the configuration setting
master
to set the hostname, and the port or socket of the MySQL master server.
MySQL slave servers are configured using the
slave
keyword.
Minimal plugin-specific configuration file (mysqlnd_ms_plugin.ini)
Configuring a MySQL slave server list is required, although it may
contain an empty list. It is recommended to always configure at
least one slave server.
Server lists can use
anonymous or non-anonymous syntax. Non-anonymous
lists include alias names for the servers, such as master_0
for the master in the above example. The quickstart uses the
more verbose non-anonymous syntax.
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 sections about
connection pooling and switching,
transaction handling,
fail over
load balancing and
read-write splitting all provide
more details. And potential pitfalls are described later in this guide.
It is the responsibility of the application to handle potential issues caused
by connection switches, by configuring a master with at least one slave
server, which allows switching to work therefore related problems can be found.
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, and
PDO_MYSQL) that is
compiled to use the mysqlnd library.
PECL/mysqlnd_ms plugs into the mysqlnd library.
It does not change the API or behavior of those extensions.
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 then
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
]]>
The connection examples above will be load balanced.
The plugin will send read-only statements to the MySQL slave server with the
IP 192.168.2.27 and will listen on port 3306
for the MySQL client connection. All other statements will be directed to the
MySQL master server running on the host localhost. If on Unix like
operating systems, the master on localhost will be accepting
MySQL client connections on the Unix domain socket /tmp/mysql.sock,
while TCP/IP is the default port on Windows.
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 is not a general limitation. As of PECL/mysqlnd_ms 1.1.0,
it is possible to set the
username and
password for any server in the
plugins configuration file, to be used instead of the credentials passed
to the API call.
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.similar;
Connection state
The plugin changes the semantics of a PHP MySQL connection handle.
A new connection handle represents a connection pool, instead of a
single MySQL client-server network connection. The connection pool consists
of a master connection, and optionally any number of 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.
For a complete list of items that belong to the state of a connection, see the
connection pooling and switching
concepts documentation.
If the plugin decides to switch connections for load balancing, the
application could be given a 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 opens 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 force a query to choose a specific server from the connection pool.
It gives the plugin a hint to use a designated server, which 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 interfere with other programs such as the MySQL Server, the MySQL Proxy,
or a firewall.
Three SQL hints are supported by the plugin:
The MYSQLND_MS_MASTER_SWITCH hint 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 was used for the previous statement.
The plugin scans the beginning of a statement for the existence of an SQL
hint. SQL hints are only recognized if they appear at the 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 above example, using MYSQLND_MS_LAST_USED_SWITCH prevents
session 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
are typically 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);
}
?>
]]>
A use case may include the creation of tables on a slave.
If an SQL hint is not given, then 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 a
connection, and forces 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.4.0, 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.4.0 or newer, API calls that enable autocommit mode,
and when 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 prevents connection switches in the middle of
a transaction. Once autocommit is re-enabled, 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();
?>
]]>
Experimental PHP 5.4.0+ feature
The plugin configuration option
trx_stickiness=master
is an experimental feature, and requires PHP 5.4.0 or newer.