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 myapp Plugin 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 master Pitfall: 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 master SQL 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 master Using 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 setting Outlook: 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.