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 myapp Plugin 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 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 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 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 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 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.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 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(); ?> ]]> The plugin configuration option trx_stickiness=master is an experimental feature. It requires PHP 5.3.99.