mirror of
https://github.com/php/doc-es.git
synced 2026-03-29 10:52:21 +02:00
git-svn-id: https://svn.php.net/repository/phpdoc/es/trunk@337843 c90b9560-bf6c-de11-be94-00142212c4b1
2387 lines
95 KiB
XML
2387 lines
95 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- $Revision$ -->
|
|
<!-- EN-Revision: b2640f8900175a34675e303ac3f8878aff8d4a54 Maintainer: seros Status: ready -->
|
|
<!-- Reviewed: no -->
|
|
<chapter xml:id="mysqlnd-ms.quickstart" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
<title>Guía rápida y ejemplos</title>
|
|
<para>
|
|
El complemento de equilibrado de carga y replicación de mysqlnd es fácil de usar.
|
|
Esta guía rápida mostrará los casos de uso típicos, y proporcionará consejos sobre cómo
|
|
empezar a utilizarlo.
|
|
</para>
|
|
<para>
|
|
Se recomienda leer las secciones de referencia además de esta guía
|
|
rápida. Aquí se intenta evitar tratar con conceptos teóricos
|
|
y limitaciones. En su lugar, se enlazará con las secciones de referencia. Es seguro
|
|
comenzar con esta guía rápida. Sin embargo, antes de usar el complemento en entornos de objetivos
|
|
críticos, le animamos a que lea también la información de fondo de las
|
|
secciones de referencia.
|
|
</para>
|
|
<para>
|
|
El enfoque es usar mysqlnd_ms de PECL para trabajar con clústeres MySQL asíncronos,
|
|
a saber, la replicación MySQL. Generalmente hablando, un clúster asíncrono es más
|
|
difícil de usar que uno sincrónico. Por lo tanto, los usuarios de, por ejemplo, el Clúster MySQL,
|
|
encontrarán más información de la necesaria.
|
|
</para>
|
|
<section xml:id="mysqlnd-ms.quickstart.configuration">
|
|
<title>Puesta en marcha</title>
|
|
<para>
|
|
El complemento está implementado como una extensión de PHP. Vea también las
|
|
<link linkend="mysqlnd-ms.installation">instrucciones de instalación</link> para
|
|
instalar la extenxión
|
|
<link xlink:href="&url.pecl.package;mysqlnd_ms">PECL/mysqlnd_ms</link>.
|
|
</para>
|
|
<para>
|
|
Compile o configure la extensión (API) de MySQL para PHP (<link linkend="ref.mysqli">mysqli</link>,
|
|
<link linkend="ref.pdo-mysql">PDO_MYSQL</link>,
|
|
<link linkend="ref.mysql">mysql</link>) que planee usar con soporte
|
|
para la biblioteca <link linkend="book.mysqlnd">mysqlnd</link>. PECL/mysqlnd_ms
|
|
es un complemento para la biblioteca mysqlnd. Para utilizar el complemento con cualqier extensión
|
|
de MySQL para PHP, la extensión ha de utilizar la biblioteca mysqlnd.
|
|
</para>
|
|
<para>
|
|
Después carque la extensión en PHP y active el complemento en el fichero de
|
|
confuguración de PHP con la directiva de configuración llamada
|
|
<link linkend="ini.mysqlnd-ms.enable">mysqlnd_ms.enable</link>.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Habilitar el complemento (php.ini)</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
mysqlnd_ms.enable=1
|
|
mysqlnd_ms.config_file=/ruta/al/complemento_mysqlnd_ms.ini
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
El complemento utiliza su propio fichero de configuración. Use la directiva
|
|
de configuración de PHP
|
|
<link linkend="ini.mysqlnd-ms.config-file">mysqlnd_ms.config_file</link>
|
|
para establecer la ruta completa al fichero de configuración específico del complemento.
|
|
Este fichero debe ser legible por PHP (p.ej., el usuario del servidor web).
|
|
Por favor, observe que la directiva de configuración <link linkend="ini.mysqlnd-ms.config-file">mysqlnd_ms.config_file</link>
|
|
sustituye a <link linkend="ini.mysqlnd-ms.ini-file">mysqlnd_ms.ini_file</link>
|
|
desde la versión 1.4.0. Es un error común usar la directiva de configuración antigua,
|
|
la cual ya no está disponible.
|
|
</para>
|
|
<para>
|
|
Cree un fichero de configuración específico del complemento. Guarde el fichero en la ruta
|
|
establecida por la directiva de configuración de PHP
|
|
<link linkend="ini.mysqlnd-ms.config-file">mysqlnd_ms.config_file</link>.
|
|
</para>
|
|
<para>
|
|
El <link linkend="mysqlnd-ms.plugin-ini-json">fichero de configuración</link> del complemento
|
|
está basado en <acronym>JSON</acronym>. Está dividido en una o más secciones.
|
|
Cada sección tiene un nombre, por ejemplo, <literal>myapp</literal>. Cada sección
|
|
contiene su propio conjunto de ajustes de configuración.
|
|
</para>
|
|
<para>
|
|
Una sección debe incluir, como mínimo, el servidor maestro de replicación MySQL y establecer
|
|
una lista de esclavos. El complemento admite únicamente un servidor maestro por sección.
|
|
La configuración de replicación MySQL de múltiples maestros aún no está totalemnte soportada.
|
|
Use el ajuste de configuración
|
|
<link linkend="ini.mysqlnd-ms-plugin-config-v2.master">master</link>
|
|
para establecer el nombre del anfitrión y el puerto o socket del servidor maestro de MySQL.
|
|
Los servidores esclavos de MySQL se configuran usando la
|
|
palabra clave
|
|
<link linkend="ini.mysqlnd-ms-plugin-config-v2.slave">slave</link>.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Fichero de configuración mínima específico del complemento (mysqlnd_ms_plugin.ini)</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost"
|
|
}
|
|
},
|
|
"slave": [
|
|
|
|
]
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Es necesario configurar una lista de servidores esclavos de MySQL, aunque puede
|
|
contener una lista vacía. Se recomienda que siempre se configure al
|
|
menos un servidor esclavo.
|
|
</para>
|
|
<para>
|
|
Las listas de servidores pueden usar la <link linkend="mysqlnd-ms.plugin-ini-json.server-list-syntax">
|
|
sintaxis anónima o no anónima</link>. Las listas no
|
|
anónimas incluyen sobrenombres para los servidores, tales como <literal>master_0</literal>
|
|
para el maestro del ejemplo de arriba. Esta guía rápida utiliza la
|
|
sintaxis no anónima más prolija.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Configuración mínima recomendada específica del complemento (mysqlnd_ms_plugin.ini)</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.2.27",
|
|
"port": "3306"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Si al menos existen
|
|
dos servidores en total, el complemento puede empezar a equilibrar la carga e intercambiar
|
|
conexiones. El intercambio de conexiones no siempre es transparente y puede ocasionar
|
|
problemas en ciertos casos. Las secciones de referencia sobre
|
|
<link linkend="mysqlnd-ms.pooling">intercambio y agrupación de conexiones</link>,
|
|
<link linkend="mysqlnd-ms.transaction">manejo de transacciones</link>,
|
|
<link linkend="mysqlnd-ms.failover">tolerancia a fallos</link>
|
|
<link linkend="mysqlnd-ms.loadbalancing">equilibrado de carga</link> y
|
|
<link linkend="mysqlnd-ms.rwsplit">división de lectura-escritura</link> proporcionan
|
|
más detalles. Los problemas potenciales están descritos más tarde en esta guía.
|
|
</para>
|
|
<para>
|
|
Es responsabilidad de la aplicación manejar los problemas potenciales causados
|
|
por el intercambio de conexiones, configurando un servidor maestro con al menos un servidor
|
|
esclavo, lo cual permite el intercambio para trabajar con los problemas relacionados que se puedan encontrar.
|
|
</para>
|
|
<para>
|
|
Los servidores maestros y esclavos de MySQL que se configuren no necesitan
|
|
ser parte de la configuración de replicación MySQL. Para realizar pruebas, se puede usar un único
|
|
servidor MySQL y hacerle saber al complemento que se trata de un servidor maestro y esclavo,
|
|
como se muestra abajo. Esto podría ser de ayuda para detectar muchos problemas potenciales con
|
|
los intercambios de conexión. Sin embargo, tal configuración no será propensa a los problemas
|
|
causados por la demora de replicación.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Usar un servidor como maestro y esclavo (¡solamente para pruebas!)</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "127.0.0.1",
|
|
"port": "3306"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
El complemento intenta notificarle de configuraciones inválidas. Desde 1.5.0,
|
|
lanzará una advertencia durante el arranque de PHP si no se puede leer el fichero de configuración,
|
|
está vacío, o falló el análisis de JSON. Según la configuración de PHP, estos
|
|
errores podrían aparecer únicamente en algunos ficheros log. Se hace una validación extra cuando se
|
|
establece una conexión y se buscan secciones válidas en el fichero de configuración.
|
|
Establecer <link linkend="ini.mysqlnd-ms.force-config-usage">mysqlnd_ms.force_config_usage</link>
|
|
podría ayudar a depurar una configuración incorrecta. Véanse también las
|
|
<link linkend="mysqlnd-ms.plugin-ini-json.debug_config">notas de depuración del fichero de configuración</link>.
|
|
</para>
|
|
|
|
</section>
|
|
|
|
<section xml:id="mysqlnd-ms.quickstart.usage">
|
|
<title>Ejecutar sentencias</title>
|
|
<para>
|
|
El complemento se puede usar con cualquier extensión de MySQL para PHP
|
|
(<link linkend="ref.mysqli">mysqli</link>,
|
|
<link linkend="ref.mysql">mysql</link>, y
|
|
<link linkend="ref.pdo-mysql">PDO_MYSQL</link>) que esté
|
|
compilada para utilizar la biblioteca <link linkend="book.mysqlnd">mysqlnd</link>.
|
|
<literal>PECL/mysqlnd_ms</literal> se acopla a la biblioteca <link linkend="book.mysqlnd">mysqlnd</link>.
|
|
No cambia la API o el comportamiento de estas extensiones.
|
|
</para>
|
|
<para>
|
|
Siempre que se esté abriendo una conexión a MySQL, el complemento compara el valor del
|
|
parámetro 'host' de la llamada de conexión con los nombres de las secciones
|
|
del fichero de configuración específico del complemento. Si, por ejemplo, el
|
|
fichero de configuración específico del complemento tiene una sección <literal>myapp</literal> , se
|
|
ha de hacer referencia a ésta para abrir una conexión a MySQL para el
|
|
anfitrión <literal>myapp</literal>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Fichero de configuración específico del complemento (mysqlnd_ms_plugin.ini)</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.2.27",
|
|
"port": "3306"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Abrir una conexión con equilibrado de carga</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
/* El equilibrado de carga sigue las reglas de la sección "myapp" del fichero de configuración del complemento */
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
$pdo = new PDO('mysql:host=myapp;dbname=base_datos', 'nombre_usuario', 'contraseña');
|
|
$mysql = mysql_connect("myapp", "nombre_usuario", "contraseña");
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Los ejemplos de conexión de arriba tendrán equilibrio de carga.
|
|
El complemento enviará sentencias de sólo lectura al servidor esclavo de MySQL con la
|
|
IP <literal>192.168.2.27</literal> y escuchará el puerto <literal>3306</literal>
|
|
para la conexión cliente de MySQL. Todas las demás sentencias serán dirigidas al
|
|
servidor maestro de MySQL que se ejecuta en el anfitrión <literal>localhost</literal>. Si se ejecuta en sistemas
|
|
operativos de tipo Unix, el maestro sobre <literal>localhost</literal> aceptará
|
|
conexiones cliente de MySQL en el socket de dominio Unix <literal>/tmp/mysql.sock</literal>,
|
|
mientras que TCP/IP es el puerto predeterminado en Windows.
|
|
El complemento usará el nombre de usuario <literal>nombre_usuario</literal> y la contraseña
|
|
<literal>contraseña</literal> para conectarse a cualquier servidor de MySQL incluido en
|
|
la sección <literal>myapp</literal> del fichero de configuración del complemento. Una vez
|
|
realizada la conexión, el complemento seleccionará <literal>base_datos</literal> como el esquema
|
|
actual.
|
|
</para>
|
|
<para>
|
|
El nombre de usuario, la contraseña y el nombre del esquema son tomados de las llamadas a la
|
|
API de conexión y usados por todos los servidores. En otras palabras: se deben usar los mismos
|
|
nombre de usuario y contraseña para cada servidor MySQL incluido en una sección del fichero de configuración
|
|
del comlemento. No es una limitación generalizada. A partir de <literal>PECL/mysqlnd_ms</literal> 1.1.0,
|
|
es posible establecer
|
|
<link linkend="mysqlnd-ms.plugin-ini-json.server-config-keywords">username</link> y
|
|
<link linkend="mysqlnd-ms.plugin-ini-json.server-config-keywords">password</link> para cualquier servidor del
|
|
fichero de configuración del complemento, para utilizarlos en lugar de las credenciales pasadas
|
|
a la llamada a la API.
|
|
</para>
|
|
<para>
|
|
El complemento no cambia la API para ejecutar sentencias.
|
|
La <link linkend="mysqlnd-ms.rwsplit">división de lectura-escritura</link>
|
|
se desarrolla aparte. El siguiente ejemplo asume que no existe
|
|
una demora de replicación significante entre el maestro y el esclavo.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Ejecutar sentencias</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
/* El equilibrado de carga sigue las reglas de la sección "myapp" del fichero de configuración del complemento */
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (mysqli_connect_errno()) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* Las sentencias se ejcutarán en el maestro */
|
|
if (!$mysqli->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);
|
|
}
|
|
|
|
/* solo lectura: la sentencia se ejecutará en un esclavo */
|
|
if (!($res = $mysqli->query("SELECT id FROM test"))) {
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
} else {
|
|
$fila = $res->fetch_assoc();
|
|
$res->close();
|
|
printf("El esclavó devuelve el ID = '%s'\n", $fila['id'];
|
|
}
|
|
$mysqli->close();
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs.similar;
|
|
<screen>
|
|
<![CDATA[
|
|
El esclavó devuelve el ID = '1'
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
</section>
|
|
|
|
<section xml:id="mysqlnd-ms.quickstart.connectionpooling">
|
|
<title>Estado de la conexión</title>
|
|
<para>
|
|
El complemento cambia la semántica de un gestor de conexión de MySQL para PHP.
|
|
Un gestor de conexión representa una agrupación de conexiones, en lugar de una
|
|
única conexión de red cliente-servidor de MySQL. La agrupación de conexiones consiste
|
|
en una conexión maestra, y opcionalmente cualquier número de conexiones esclavas.
|
|
</para>
|
|
<para>
|
|
Cada conexión de la agrupación de conexiones tiene su propio estado. Por ejemplo,
|
|
las variables SQL de usuario, las tablas temporales y las transacciones, son parte del estado.
|
|
Se puede encontrar un listado completo con los elementos que pertenecen al estado de una conexión en
|
|
los conceptos de la documentación de
|
|
<link linkend="mysqlnd-ms.pooling">agrupación e intercambio de conexiones</link>.
|
|
Si el complemento decide intercambiar conexiones para equilibrar la carga, a la
|
|
aplicación se le podría proporcionar una conexión que tuviera un estado diferente.
|
|
Las aplicaciones deben considerar esto.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Configuración del complemento con un esclavo y un maestro</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.2.27",
|
|
"port": "3306"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Problema: estado de conexión y variables SQL de usuario</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* Conexión 1, la conexión vincula una variable SQL de usuario, no se ejecuta ningún SELECT en el maestro */
|
|
if (!$mysqli->query("SET @myrole='master'")) {
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
}
|
|
|
|
/* Conexión 2, se ejecuta en el esclavo a causa de SELECT */
|
|
if (!($res = $mysqli->query("SELECT @myrole AS _role"))) {
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
} else {
|
|
$fila = $res->fetch_assoc();
|
|
$res->close();
|
|
printf("@myrole = '%s'\n", $fila['_role']);
|
|
}
|
|
$mysqli->close();
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
@myrole = ''
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
El ejemplo abre una conexión con equilibrio de carga y ejecuta dos sentencias.
|
|
La primera sentencia <literal>SET @myrole='master'</literal> no comienza
|
|
con la cadena <literal>SELECT</literal>. Por lo tanto, el complemento no
|
|
la reconoce como una consulta de solo lectura que se ejecutaría en un esclavo. El
|
|
complemento ejecuta la sentencia en una conexión al maestro. La sentencia
|
|
establece una variable SQL de usuario que está vinculada a la conexión con el maestro. El
|
|
estado de la conexión maestra ha sido cambiado.
|
|
</para>
|
|
<para>
|
|
La siguiente sentencia es <literal>SELECT @myrole AS _role</literal>.
|
|
El complemento la reconoce como una consulta de solo lectura y la envía al
|
|
esclavo. La sentencia se ejecuta en una conexión al esclavo. Esta
|
|
segunda conexión no tiene ninguna variable SQL de usuario vinculada a ella.
|
|
Tiene un estado diferente a la primera conexión al maestro.
|
|
La variable SQL de usuario no está establecida. El script del ejemplo imprime
|
|
<literal>@myrole = ''</literal>.
|
|
</para>
|
|
<para>
|
|
Es responsabilidad del desarrollador de la apliación ocuparse
|
|
del estado de la conexión. El complemento no monitoriza todas
|
|
las actividades de cambio de estado. La monitorización de todos los casos posibles
|
|
sería una tarea intensiva de la CPU, si es que esta tarea pudiera ser realizada.
|
|
</para>
|
|
<para>
|
|
Los problemas se pueden evitar fácilmente usando sugerencias SQL.
|
|
</para>
|
|
</section>
|
|
|
|
<section xml:id="mysqlnd-ms.quickstart.sqlhints">
|
|
<title>Sugerencias SQL</title>
|
|
<para>
|
|
Las sugerencias SQL pueden forzar a una consulta a elegir un servidor específico de la agrupación de conexiones.
|
|
Proporcionan al complemento una recomendación para utilizar un servidor indicado, lo que puede resolver
|
|
problemas causados por intercambios de conexión y estados de conexión.
|
|
</para>
|
|
<para>
|
|
Las sugerencias SQL son comentarios que siguen el estándar. Ya que
|
|
los comentarios SQL son ignorados por sistemas de procesamiento SQL,
|
|
no interfieren con otros programas como el Servidor MySQL, el Proxy MySQL,
|
|
o un cortafuegos.
|
|
</para>
|
|
<para>
|
|
El complemento admite tres sugerencias SQL: La
|
|
sugerencia <constant>MYSQLND_MS_MASTER_SWITCH</constant> hace que el complemento ejecute una
|
|
sentencia en el maestro, <constant>MYSQLND_MS_SLAVE_SWITCH</constant>
|
|
fuerza el uso del esclavo, y
|
|
<constant>MYSQLND_MS_LAST_USED_SWITCH</constant> ejecutará una sentencia en
|
|
el mismo servidor que se utilizó con la sentencia anterior.
|
|
</para>
|
|
<para>
|
|
El complemento examina el comienzo de una sentencia para la existencia de una sugerencia
|
|
SQL. Las sugerencias SQL solo son reconocidas si aparecen al comienzo de
|
|
la sentencia.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Configuración del complemento con un esclavo y un maestro</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.2.27",
|
|
"port": "3306"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Sugerencias SQL para evitar el intercambio de conexión</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (mysqli_connect_errno()) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* Conexión 1, la conexión vincula una variable SQL de usuario, no se ejecuta ningún SELECT en el maestro */
|
|
if (!$mysqli->query("SET @myrole='master'")) {
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
}
|
|
|
|
/* Conexión 1, se ejecuta en el maestro debido a la sugerencia SQL */
|
|
if (!($res = $mysqli->query(sprintf("/*%s*/SELECT @myrole AS _role", MYSQLND_MS_LAST_USED_SWITCH)))) {
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
} else {
|
|
$fila = $res->fetch_assoc();
|
|
$res->close();
|
|
printf("@myrole = '%s'\n", $fila['_role']);
|
|
}
|
|
$mysqli->close();
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
@myrole = 'master'
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
En el ejemplo de arriba, el uso de <constant>MYSQLND_MS_LAST_USED_SWITCH</constant> evita
|
|
el intercambio de sesión desde el maestro al esclavo al ejecutar la sentencia
|
|
<literal>SELECT</literal>.
|
|
</para>
|
|
<para>
|
|
Las sugerencias SQL también se pueden usar para ejecutar sentencias <literal>SELECT</literal>
|
|
en el servidor maestro de MySQL. Esto podría ser necesario si los servidores esclavos de MySQL
|
|
están normalmente detrás del maestro, pero no se necesitan datos actuales del clúster.
|
|
</para>
|
|
<para>
|
|
En la versión 1.2.0, el concepto de nivel de servicio ha sido introducido para dirigir
|
|
casos en los que no son necesarios datos actuales. El uso de un nivel de servicio requiere menos atención
|
|
y elimina la necesidad de usar sugerencias SQL para este caso. Se puede encontrar más
|
|
información más abajo en la sección de nivel de servicio y consistencia.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Enfrentarse a la demora de replicación</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* Forzar el uso del maestro, el maestro siempre tiene datos recientes y actuales */
|
|
if (!$mysqli->query(sprintf("/*%s*/SELECT critical_data FROM important_table", MYSQLND_MS_MASTER_SWITCH))) {
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Un caso de uso puede incluir la creación de tablas en un esclavo.
|
|
Si no se proporciona una sugenrencia SQL, el complemento enviará las sentencias
|
|
<literal>CREATE</literal> e <literal>INSERT</literal> al maestro. Utilice la
|
|
sugerencia SQL <constant>MYSQLND_MS_SLAVE_SWITCH</constant> si quiere
|
|
ejecutar tales sentencias en un esclavo, por ejemplo, para construir tablas de
|
|
informes temporales.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Creación de una tabla en un esclavo</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* Forzar el uso del esclavo */
|
|
if (!$mysqli->query(sprintf("/*%s*/CREATE TABLE informes_esclavo(id INT)", MYSQLND_MS_SLAVE_SWITCH))) {
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
}
|
|
/* Continuar usando esta conexión esclava en particular */
|
|
if (!$mysqli->query(sprintf("/*%s*/INSERT INTO informes_esclavo(id) VALUES (1), (2), (3)", MYSQLND_MS_LAST_USED_SWITCH))) {
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
}
|
|
/* ¡No usar MYSQLND_MS_SLAVE_SWITCH, ya que permitiría el intercambio con otro esclavo! */
|
|
if ($res = $mysqli->query(sprintf("/*%s*/SELECT COUNT(*) AS _num FROM informes_esclavo", MYSQLND_MS_LAST_USED_SWITCH))) {
|
|
$fila = $res->fetch_assoc();
|
|
$res->close();
|
|
printf("Hay %d filas en la tabla 'informes_esclavo'", $fila['_num']);
|
|
} else {
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
}
|
|
$mysqli->close();
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
La sugerencia SQL <constant>MYSQLND_MS_LAST_USED</constant> prohíbe el intercambio de una
|
|
conexión, por lo que fuerza el uso de la conexión utilizada anteriormente.
|
|
</para>
|
|
</section>
|
|
|
|
<section xml:id="mysqlnd-ms.quickstart.transactions">
|
|
<title>Transacciones locales</title>
|
|
<para>
|
|
La versión actual del complemento no es segura con transacciones de forma predeterminada,
|
|
ya que no considera su ejecución en ningún caso. Las transacciones SQL son
|
|
unidades de trabajo para ejecutarlas en un único servidor. El complemento no siempre sabe
|
|
cuándo comienza y cuándo finaliza una unidad de trabajo. Por lo tanto, el complemento podría
|
|
decidir intercambiar conexiones en mitad de una transacción.
|
|
</para>
|
|
<para>
|
|
Ningún tipo de equilibrador de carga de MySQL puede detectar los límites de una transacción sin ningún
|
|
tipo de sugerencia por parte de la aplicación.
|
|
</para>
|
|
<para>
|
|
Se pueden usar sugerencias SQL para superar esta limitación. De forma alternativa,
|
|
se puede activar la monitorización de llamadas a la API de transacciones. En el último caso,
|
|
de deben usar llamadas a la API solamente para controlar transacciones. Véase más abajo.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Configuración del complemento con un esclavo y un maestro</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
[myapp]
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.2.27",
|
|
"port": "3306"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Usar sugerencias SQL para transacciones</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* No es un SELECT, se usará el maestro */
|
|
if (!$mysqli->query("START TRANSACTION")) {
|
|
/* Por favor, use un manejo de errores mejor en su código */
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* ¡Evitar el intercambio de conexión! */
|
|
if (!$mysqli->query(sprintf("/*%s*/INSERT INTO test(id) VALUES (1)", MYSQLND_MS_LAST_USED_SWITCH))) {
|
|
/* Por favor, realice el ROLLBACK apropiado en su código, y no use simplemente '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))) {
|
|
$fila = $res->fetch_assoc();
|
|
$res->close();
|
|
if ($fila['_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();
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Desde PHP 5.4.0, la biblioteca <literal>mysqlnd</literal> permite al
|
|
complemento monitorizar el estado del modo <literal>autocommit</literal>, si
|
|
el modo está establecido por llamadas a la API en lugar de usar sentencias SQL como
|
|
<literal>SET AUTOCOMMIT=0</literal>. Esto permite que el complemento
|
|
considere las transacciones. En este caso, es necesario usar sugerencias SQL.
|
|
</para>
|
|
<para>
|
|
Si se está utlizando PHP 5.4.0 o superior, con las llamadas a la API que habilitan el modo <literal>autocommit</literal>,
|
|
y cuando se establece la opción de configuración del complemento
|
|
<link linkend="ini.mysqlnd-ms-plugin-config-v2.trx-stickiness">trx_stickiness=master</link>,
|
|
éste puede deshabilitar automáticamente el equilibrado de carga y el intercambio de conexiones
|
|
para transacciones SQL. Con esta configuración, el complemento detiene el equilibrado de carga
|
|
si <literal>autocommit</literal> está deshabilitado, y dirige todas las sentencias al
|
|
maestro. Esto evita el intercambio de conexiones en mitad de
|
|
una transacción. Una vez que <literal>autocommit</literal> es rehabilitado, el complemento
|
|
inicia de nuevo las sentencias con equilibrado de carga.
|
|
</para>
|
|
<para>
|
|
La detección de los límites de transacciones basada en API ha sido mejorada con PHP 5.5.0 y
|
|
<literal>PECL/mysqlnd_ms</literal> 1.5.0 para cubirir no solo llamadas a <function>mysqli_autocommit</function>,
|
|
sino también a <function>mysqli_begin</function>,
|
|
<function>mysqli_commit</function> y <function>mysqli_rollback</function>.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Equilibrado de carga considerando transacciones: el ajuste trx_stickiness</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "127.0.0.1",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"trx_stickiness": "master"
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Consideración de transacciones</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* Deshabilitar autocommit, el complemento ejecutará todas las sentencias en el maestro */
|
|
$mysqli->autocommit(FALSE);
|
|
|
|
if (!$mysqli->query("INSERT INTO test(id) VALUES (1)")) {
|
|
/* Por favor, realice el ROLLBACK apropiado en su código, y no use simplemente 'die' */
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
if ($res = $mysqli->query("SELECT COUNT(*) AS _num FROM test")) {
|
|
$fila = $res->fetch_assoc();
|
|
$res->close();
|
|
if ($fila['_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));
|
|
}
|
|
|
|
/* El complemento asume que la transacción ha finalizado e inicia el equilibrado de carga de nuevo */
|
|
$mysqli->autocommit(true);
|
|
$mysqli->close();
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<note>
|
|
<title>Requisitos de versión</title>
|
|
<para>
|
|
La opción de configuración del complemento
|
|
<link linkend="ini.mysqlnd-ms-plugin-config-v2.trx-stickiness">trx_stickiness=master</link>
|
|
requiere PHP 5.4.0 o superior.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
Por favor, observe las restricciones descritas en la
|
|
sección de conceptos sobre
|
|
<link linkend="mysqlnd-ms.transaction">manejo de transacciones</link>.
|
|
</para>
|
|
</section>
|
|
|
|
<section xml:id="mysqlnd-ms.quickstart.xa_transactions">
|
|
<title>Transacciones XA/Distribuidas</title>
|
|
<note>
|
|
<title>Requisitos de versión</title>
|
|
<para>
|
|
Las funciones relacionadas con XA han sido introducidas en la verisón 1.6.0-alpha de PECL mysqlnd_ms.
|
|
</para>
|
|
</note>
|
|
<note>
|
|
<title>Se buscan los primeros adaptadores</title>
|
|
<para>
|
|
Esta característica está actualmente en desarrollo. Podrían existir problemas y/o
|
|
limitaciones. No la use en entornos de producción, aunque
|
|
las primeras pruebas indican una calidad razonable.
|
|
</para>
|
|
<para>
|
|
Por favor, contacte con el equipo de desarrollo si está interesado en esta característica.
|
|
Estamos buscando comentarios de la vida real para completar esta característica.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
Las transacciones XA son un método estandarizado para ejecutar transacciones a través
|
|
de varios recursos. Estos recursos pueden ser bases de datos u otros sistemas
|
|
transaccionales. El servidor de MySQL admite sentencias SQL de XA, las cuales permiten a los usuarios
|
|
llevar a cabo una transacción SQL distribuida que genera varios servidores de bases de datos,
|
|
o cualquier otro tipo siempre y cuando admita también las sentencias SQL. En este
|
|
tipo de escenario, es responsabilidad del usuario coordinar los servidores
|
|
participantes.
|
|
</para>
|
|
<para>
|
|
<literal>PECL/mysqlnd_ms</literal> puede actuar como un coordinador de transacciones para una transacción
|
|
global (distribuida, XA) se lleve a cabo solamente en servidores de MySQL. Como coordinador transacciones, el complemento
|
|
rastrea todos los servidores involucrados en una transacción global y envía de forma transparente
|
|
las sentencias SQL apropiadas a los participantes. Las transacción globales son controladas con
|
|
<function>mysqlnd_ms_xa_begin</function>, <function>mysqlnd_ms_xa_commit</function>
|
|
y <function>mysqlnd_ms_xa_rollback</function>. Los detalles SQL mayormente están ocultos a
|
|
la aplicación, ya que es necesario rastrear y coordinar a los participantes.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Patrón general para transacciones XA</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* iniciar una transacción global */
|
|
$gtrid_id = "12345";
|
|
if (!mysqlnd_ms_xa_begin($mysqli, $gtrid_id)) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* ejecutar las consultas como siempre: XA BEGIN será inyectada en cuanto se ejecute una consulta */
|
|
if (!$mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')")) {
|
|
/* Tanto si falla INSERT o falla la inyección de XA BEGIN */
|
|
if ('XA' == substr($mysqli->sqlstate, 0, 2)) {
|
|
printf("Fallo relacionado con una transacción/XA global, [%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
} else {
|
|
printf("Fallo de INSERT, [%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
}
|
|
/* revertir la transacción global */
|
|
mysqlnd_ms_xa_rollback($mysqli, $xid);
|
|
die("Deteniendo.");
|
|
}
|
|
|
|
/* continuar con la realización de consultas en otros servidores, p.ej. otros fragmentos */
|
|
|
|
/* consignar la transacción global */
|
|
if (!mysqlnd_ms_xa_commit($mysqli, $xa_id)) {
|
|
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
A diferencia de las transacciones locales, la cuales se llevan a cabo en un único servidor,
|
|
las transacciones XA tienen un identificador (xid) asociado a ellas.
|
|
El identificador de transacción de XA está compuesto por un identificador de transacción
|
|
global (gtrid), un identificador de ramificación (bqual), y
|
|
un identificador de formato (formatID). Solamente el identificador de transacción global puede
|
|
y debe proporcionarse al invocar a cualquier función de XA del complemento.
|
|
</para>
|
|
<para>
|
|
Una vez que ha sido iniciada una transacción global, el complemento comienza a rastrear servidores
|
|
hsata que la transacción global finaliza. Cuando un servidor se escoge para la ejecución de una consulta,
|
|
el complemento inyecta la sentencia SQL <literal>XA BEGIN</literal> antes de
|
|
ejecutar la sentencia SQL real en el servidor. <literal>XA BEGIN</literal>
|
|
hace que el servidor participe en la transacción global. Si falla la inyección
|
|
de la sentencia SQL, el complemento informará del problema en réplica a la función
|
|
de ejecución de consultas que se empleó. En el ejemplo de arriba,
|
|
<literal>$mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')")</literal>
|
|
indicaría tal error. Se podría comprobar el código de estado SQL de los errores para
|
|
determinar la consulta real (aquí: <literal>INSERT</literal>) que ha fallado
|
|
o el error relacionado con la transacción global. Depende de usted ignorar el
|
|
fallo de iniciar la transacción global en un servidor y continuar la ejecución
|
|
sin tener al servidor participando en la transacción global.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Las transacciones locales y globales son mutuamente exclusivas</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* iniciar una transacción local */
|
|
if (!$mysqli->begin_transaction()) {
|
|
die(sprintf("[%d/%s] %s\n", $mysqli->errno, $mysqli->sqlstate, $mysqli->error));
|
|
}
|
|
|
|
/* anora no se puede iniciar la transición global - primero debe finalizar la local */
|
|
$gtrid_id = "12345";
|
|
if (!mysqlnd_ms_xa_begin($mysqli, $gtrid_id)) {
|
|
die(sprintf("[%d/%s] %s\n", $mysqli->errno, $mysqli->sqlstate, $mysqli->error));
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
|
|
Warning: mysqlnd_ms_xa_begin(): (mysqlnd_ms) Some work is done outside global transaction. You must end the active local transaction first in ... on line ...
|
|
[1400/XAE09] (mysqlnd_ms) Some work is done outside global transaction. You must end the active local transaction first
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Una transacción global no se puede iniciar cuando una transacción local está activa.
|
|
El complelento intenta detectar esta situación tan pronto como sea posible, que es cuando
|
|
se llama a <function>mysqlnd_ms_xa_begin</function>. Si solamente se emplean llamadas a la API para
|
|
controlar transacciones, el complemento sabrá qué transacción local está abierta,
|
|
devolviendo un error para <function>mysqlnd_ms_xa_begin</function>. Sin embargo, observe que
|
|
las <link linkend="mysqlnd-ms.transaction">limitaciones para detectar límites
|
|
de transacciones</link> del complemento. En el peor de los casos, si se emplea SQL directo
|
|
para transacciones locales (<literal>BEGIN</literal>,
|
|
<literal>COMMIT</literal>, ...), podría suceder que un error de demorara
|
|
hasta que se ejecute alguna sentencia SQL en el servidor.
|
|
</para>
|
|
<para>
|
|
Para finalizar una transacción global se ha de invocar a <function>mysqlnd_ms_xa_commit</function> o
|
|
a <function>mysqlnd_ms_xa_rollback</function>. Cuando finaliza una transacción global,
|
|
todos los participantes deben ser informados de dicha finalización. Por lo tando, PECL/mysqlnd_ms
|
|
enviará de forma transparente las sentencias SQL relacionadas apropiadas
|
|
a alguno o todos ellos. Cualquier fallo durante esta fase ocasionará una reversión
|
|
implícita. La API relacionada con XA se mantiene aquí intencionadamente simple. Una API
|
|
más compleja que proporcionara más control dejaría al descubierto pocas ventajas, si las hubiera,
|
|
sobre una implementación de usuario que enviara todas las sentencias SQL de XA de bajo nivel por sí misma.
|
|
</para>
|
|
<para>
|
|
Las transacciones XA emplean el protocolo de consignación de dos fases. El protocolo de consignación
|
|
de dos fases es un protocolo de bloqueo. Hay casos en los que no se puede hacer ningún
|
|
progreso, incluso al emplear tiempos de espera. Los coordinadores de transaccionesbe made, not even when using timeouts. Transaction coordinators
|
|
deberían sobrevivir a sus propios fallos, ser capaces de detectar bloqueos y deshacer empates.
|
|
<literal>PECL/mysqlnd_ms</literal> toma el papel de un coordinador de transacciones y puede ser
|
|
configurado para sobrevivir a su propia caída para evitar problemas con servidores de MySQL bloqueados.
|
|
Por tanto, el complemento puede y debería ser configurado para utilizar un estado persistente y seguro ante caídas
|
|
para permitir la recolección de basura de transacciones globales abortadas y no finalizadas.
|
|
Una transacción global puede ser abortada en un estado abierto si el complemento falla (se cae)
|
|
o falla una conexión desde el complemento a un participante de una transacción global.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Almacenamiento del estado del coordinador de transacciones</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"xa": {
|
|
"state_store": {
|
|
"participant_localhost_ip": "192.168.2.12",
|
|
"mysql": {
|
|
"host": "192.168.2.13",
|
|
"user": "root",
|
|
"password": "",
|
|
"db": "test",
|
|
"port": "3312",
|
|
"socket": null
|
|
}
|
|
}
|
|
},
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "192.168.2.14",
|
|
"port": "3306"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Actualmente, <literal>PECL/mysqlnd_ms</literal> admite solamente el uso de tablas de bases de datos de MySQL
|
|
como almacenamiento de estados. Las definiciones de SQL de las tablas se proporcionan en la
|
|
<link linkend="ini.mysqlnd-ms-plugin-config-v2.xa">sección de configuración del complemento.</link>
|
|
Asegúrese de utilizar un motor de almacenamiento transaccional y seguro ante caídas
|
|
para las tablas, como InnoDB. InnoDB es el motor de tablas
|
|
predeterminado en versiones recientes del servidor de MySQL. Asegúrese también
|
|
de que el servidor de bases de datos sea altamente disponible.
|
|
</para>
|
|
<para>
|
|
Si se ha configurado un almacén de estado, el complemento puede realizar una recolección de basura.
|
|
Durante dicha recolección, podría ser necesario conectarse a un participante
|
|
de una transacción global fallida. Así, el almacén de estado mantiene una lista de participantes
|
|
y, entre otras cosas, sus nombres de host. Si la recolección de basura se ejecuta
|
|
en otro host que no sea el que haya escrito una entrada de participación con el
|
|
el nombre de host <literal>localhost</literal>, entonces <literal>localhost</literal>
|
|
se resolverá a máquinas diferentes. Hay dos soluciones al problema.
|
|
O no se configura ningún servidor con el nombre de host <literal>localhost</literal> y
|
|
se configura una dirección IP (y puerto), o se indica la recolección de basura.
|
|
En el ejemplo anterior, <literal>localhost</literal> se usa para
|
|
<literal>master_0</literal>, de ahí que podría no resolverse al host
|
|
correcto durante la recolección de basura. Sin embargo, <literal>participant_localhost_ip</literal>
|
|
también está establecido para indicar la recolección de basura que <literal>localhost</literal>
|
|
representa para la IP <literal>192.168.2.12</literal>.
|
|
</para>
|
|
</section>
|
|
|
|
<section xml:id="mysqlnd-ms.quickstart.qos-consistency">
|
|
<title>Nivel de servicio y consistencia</title>
|
|
<note>
|
|
<title>Requisitos de versión</title>
|
|
<para>
|
|
Los niveles de servicios han sido introducidos en la versión 1.2.0-alpha de PECL mysqlnd_ms.
|
|
<function>mysqlnd_ms_set_qos</function>
|
|
está disponible con PHP 5.4.0 o superior.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
Las diferentes soluciones de clúster MySQL ofrecen diferentes niveles de
|
|
servicios y de consistencia de datos para sus usuarios. Un clúster de replicación MySQL asíncrono
|
|
ofrece la consistencia final de forma predeterminada. Una lectura ejecutada en un esclavo asíncrono
|
|
puede devolver datos actuales, antiguos, o ningún dato, dependiendo de si el esclavo
|
|
ha replicado todos los conjuntos de cambios del maestro.
|
|
</para>
|
|
<para>
|
|
Las aplicaciones que utilizan un clúster de replicación MySQL necesitan ser diseñadas para que funcionen
|
|
correctamente con datos de consistencia final. En algunos casos, sin embargo, los datos antiguos
|
|
no son aceptables. En estos casos solamente son permitidos ciertos accesos a esclavos o incluso a un maestro
|
|
para realizar la calidad de servicio requerida del clúster.
|
|
</para>
|
|
<para>
|
|
A partir de PECL mysqlnd_ms 1.2.0, el complemento puede seleccionar automáticamente
|
|
nodos de replicación de MySQL que proporcionen consistencia de sesión o
|
|
consistencia fuerte. La consistencia de sesión significa que un cliente puede leer sus escrituras.
|
|
Otros clientes pueden ver o no la escritura del cliente. La consistencia fuerte significa
|
|
que todos los clientes verán todas las escrituras del cliente.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Consistencia de sesión: lectura de sus escrituras</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "127.0.0.1",
|
|
"port": "3306"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Solicitud de consistencia de sesión</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* división de lecutura-escritura: se usa el maestro */
|
|
if (!$mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')")) {
|
|
/* Por favor, use un manejo de errores mejor en su código */
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Solicitud de consistencia de sesión: lectura de sus escrituras */
|
|
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION)) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* El complemento selecciona un nodo que posee los cambios, aquí: el maestro */
|
|
if (!$res = $mysqli->query("SELECT item FROM orders WHERE order_id = 1")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
var_dump($res->fetch_assoc());
|
|
|
|
/* Volver a la consistencia final: se permiten datos antiguos */
|
|
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL)) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* El complemento seleccina cualquier esclavo, se permiten datos antiguos */
|
|
if (!$res = $mysqli->query("SELECT item, price FROM specials")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Los niveles de servicios se pueden establecer en el fichero de configuración del complemento y
|
|
en tiempo de ejecución usando <function>mysqlnd_ms_set_qos</function>.
|
|
En el ejemplo, la función se usa para forzar la
|
|
consistencia de sesión (lectura de sus escrituras) para todas las sentencias futuras hasta próximo aviso.
|
|
La sentencia <literal>SELECT</literal> de la tabla <literal>orders</literal>
|
|
se ejecuta en el maestro para asegurarse de que las escrituras anteriores puedan ser vistas por el cliente.
|
|
La lógica de la división de lectura-escritura ha sido adaptada para satisfacer el nivel de servicio.
|
|
</para>
|
|
<para>
|
|
Después de que una aplicación haya leído sus cambios desde la tabla <literal>orders</literal>,
|
|
vuelve al nivel de servicio predeterminado, que es la consistencia final. Ésta
|
|
no pone restricciones al elegir un nodo para la ejecución de sentencias.
|
|
Por lo tanto, la sentencia <literal>SELECT</literal> de la tabla <literal>specials</literal>
|
|
se ejecuta en un esclavo.
|
|
</para>
|
|
<para>
|
|
La nueva funcionalidad sustituye el uso de sugerencias SQL y la opción
|
|
de configuración <literal>master_on_write</literal>. En muchos casos,
|
|
<function>mysqlnd_ms_set_qos</function> es más fácil de usar, más potente y
|
|
mejora la portabilidad.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Edad máxima/demora del esclavo</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "127.0.0.1",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"failover" : "master"
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Limitar la demora del esclavo</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* Demora de la lectura desde los esclavos de no más de cuatro segundos */
|
|
$ret = mysqlnd_ms_set_qos(
|
|
$mysqli,
|
|
MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL,
|
|
MYSQLND_MS_QOS_OPTION_AGE,
|
|
4
|
|
);
|
|
|
|
if (!$ret) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* El comlemento elige cualquier esclavo, el cual puede o no poseer los cambios */
|
|
if (!$res = $mysqli->query("SELECT item, price FROM daytrade")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Volver a lo predeterminado: usar todos los esclavos y maestros permitidos */
|
|
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL)) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
El nivel de servicio de consistencia final se puede usar con un parámetro
|
|
opcional para establecer la demora máxima de los esclavos elegidos. Si se establece,
|
|
el complemento comprueba <literal>SHOW SLAVE STATUS</literal> para todos
|
|
los esclavos configurados. En el caso del ejemplo, solamente los esclavos
|
|
para los que <literal>Slave_IO_Running=Yes</literal>,
|
|
<literal>Slave_SQL_Running=Yes</literal> y
|
|
<literal>Seconds_Behind_Master <= 4</literal>
|
|
sea verdadero, son tenidos en cuenta para la ejecución de la sentencia
|
|
<literal>SELECT item, price FROM daytrade</literal>.
|
|
</para>
|
|
<para>
|
|
La comprobacion de <literal>SHOW SLAVE STATUS</literal> se realiza de manera transparente desde
|
|
la perspectiva de las aplicaciones. Los errores, si los hubiera, son notificados como
|
|
advertencias. No se establecerá ningún error en el gestor de conexión. Incluso si todas
|
|
las sentencias SQL <literal>SHOW SLAVE STATUS</literal> ejecutadas por el
|
|
complemento fallan, la ejecución de las sentencias del usuario no se detienen, dado
|
|
que la toleracia a fallos del maestro está habilitada. Por lo tanto, no se requieren cambios en la aplicación.
|
|
</para>
|
|
<note>
|
|
<title>Operaciones caras y lentas</title>
|
|
<para>
|
|
La comprobación de <literal>SHOW SLAVE STATUS</literal> para todos los esclavos añade carga adicional
|
|
a la aplicación. Es una operación en segundo plano cara y lenta.
|
|
Intente minimizar su uso. Desafortunadamente, un clúster de replicación MySQL
|
|
no proporciona a los clientes la posibilidad de solicitar una lista de candidatos
|
|
desde una instancia central.
|
|
En consecuencia, no existe una manera más eficiente de comprobar la demora de los esclavos.
|
|
</para>
|
|
<para>
|
|
Por favor, observe las limitaciones y propiedades de <literal>SHOW SLAVE STATUS</literal>
|
|
tal y como están explicadas en el manual de referencia de MySQL.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
Para evitar que mysqlnd_ms emita una advertencia si no se pudieron encontrar esclavos
|
|
que se demoren no más que el número de segundos definido con respecto al maestro,
|
|
es necesario habilitar la tolerancia a fallos del maestro en el fichero de configuración del complemento.
|
|
Si no se pudieron encontrar esclavos y la tolerancia a fallos está habilitada, el complemento
|
|
eligirá un maestro para ejecutar la setencia.
|
|
</para>
|
|
<para>
|
|
Si no se pudieron encontrar esclavos y la tolerancia a fallos está deshabilitada, el complemento emitirá
|
|
una advertencia, no ejecutará la sentencia y establecerá un error
|
|
sobre la conexión.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>La tolerancia a fallos no está establecida</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "127.0.0.1",
|
|
"port": "3306"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>No hay esclavos dentro del límite de tiempo</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* Demora de la lectura desde los esclavos de no más de cuatro segundos */
|
|
$ret = mysqlnd_ms_set_qos(
|
|
$mysqli,
|
|
MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL,
|
|
MYSQLND_MS_QOS_OPTION_AGE,
|
|
4
|
|
);
|
|
|
|
if (!$ret) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* El comlemento elige cualquier esclavo, el cual puede o no poseer los cambios */
|
|
if (!$res = $mysqli->query("SELECT item, price FROM daytrade")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
|
|
/* Volver a lo predeterminado: usar todos los esclavos y maestros permitidos */
|
|
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL)) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
PHP Warning: mysqli::query(): (mysqlnd_ms) Couldn't find the appropriate slave connection. 0 slaves to choose from. Something is wrong in %s on line %d
|
|
PHP Warning: mysqli::query(): (mysqlnd_ms) No connection selected by the last filter in %s on line %d
|
|
[2000] (mysqlnd_ms) No connection selected by the last filter
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
</section>
|
|
<section xml:id="mysqlnd-ms.quickstart.gtid">
|
|
<title>ID de transacción globales</title>
|
|
<note>
|
|
<title>Requisitos de versión</title>
|
|
<para>
|
|
Se ha introducido una inyección de ID de transacciones global en el lado del cliente en la versión 1.2.0-alpha de mysqlnd_ms.
|
|
Esta característica no es necesaria para clústeres sincrónicos, tales como el Clúster MySQL.
|
|
Utilícela con clústeres asíncronos tales como la replicación MySQL clásica.
|
|
</para>
|
|
<para>
|
|
A partir de la versión candidata MySQL 5.6.5-m8, el servidor MySQL introduce los identificadores de transacciones globales internos.
|
|
La característica del ID de transacciones global interno está soportada por <literal>PECL/mysqlnd_ms</literal> 1.3.0-alpha o
|
|
posterior. Sin embargo, el conjunto final de características encontrado en las versiones de producción de MySQL 5.6 hasta la fecha,
|
|
no es suficiente para soportar las ideas tratadas abajo en todos los casos. Véase también la
|
|
<link linkend="mysqlnd-ms.gtid">sección de conceptos</link>.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
<literal>PECL/mysqlnd_ms</literal> puede usar tanto su propia emulación del ID de transacciones global como la
|
|
característica del ID de transacciones global interna de MySQL 5.6.5-m8 o posterior. Desde la perspectiva del
|
|
desarrolador, los enfoques del lado del cliente y del lado del servidor ofrecen las mismas características en cuanto
|
|
a niveles de servicios proporcionados por PECL/mysqlnd_ms. Las diferencias
|
|
son tratadas en la <link linkend="mysqlnd-ms.gtid">sección de conceptos</link>.
|
|
</para>
|
|
<para>
|
|
Esta guía rápida primero demuestra el uso de la emulación interna del ID de transacciones global en el
|
|
lado del cliente en <literal>PECL/mysqlnd_ms</literal> antes de mostrar cómo usar su homólogo en el lado del servidor.
|
|
Este orden asegura que la idea subyacente se trata primero.
|
|
</para>
|
|
<para>
|
|
<emphasis role="bold">La idea y la emulación en el lado del cliente</emphasis>
|
|
</para>
|
|
<para>
|
|
En su forma más básica, un ID de Transacciones Global (GTID de sus siglas en inglés) es un contador de una tabla del
|
|
maestro. El contador se incrementa siempre que se consigne una transacción en el maestro.
|
|
Los esclavos replican la tabla. El contador sirve para dos propósitos. En caso de un
|
|
fallo del maestro, ayuda al administrador de la base de datos a identificar al esclavo que fue
|
|
promovido más recientemente como nuevo maestro. Este esclavo es aquel con el
|
|
valor de contador más alto. Las aplicaciones pueden usar el ID de transacciones global para buscar
|
|
los esclavos que ya han replicado una escritura en particular (identificada por un ID de transacciones
|
|
global).
|
|
</para>
|
|
<para>
|
|
<literal>PECL/mysqlnd_ms</literal> puede inyectar SQL para cada transacción consignada para incrementar un contador GTID.
|
|
El ID creado es accesible por la aplicación para poder identificar una operación de escritura
|
|
de una aplicación. Esto habilita al complemento para proporcionar el nivel de servicio de consistencia de sesión
|
|
(lectura de sus escrituras) mediante no solamente la consulta a los maestros, sino también a los esclavos que ya han
|
|
replicado el cambio. La carga de lectura se le quita al maestro.
|
|
</para>
|
|
<para>
|
|
La emulación del ID de transacciones global en el lado del cliente tiene algunas limitaciones. Por favor,
|
|
lea la <link linkend="mysqlnd-ms.gtid">sección de conceptos</link>
|
|
detenidamente para comprender completamente los principios y las ideas
|
|
subyacentes antes de usarla en entornos de producción. No es necesario un conocimiento
|
|
profundo para continuar con esta guía rápida.
|
|
</para>
|
|
<para>
|
|
Primero, cree una tabla contador en su servidor maestro e inserte un registro en ella.
|
|
El complemento no asiste en la creación de la tabla.
|
|
Los administradores de la base de datos deben asegurarse de que existe. Dependiendo del modo de notificación
|
|
de errores, el complemento ignorará de forma silenciosa la ausencia de la tabla o abandonará.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Crear una tabla contador en el maestro</title>
|
|
<programlisting role="sql">
|
|
<![CDATA[
|
|
CREATE TABLE `trx` (
|
|
`trx_id` int(11) DEFAULT NULL,
|
|
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1
|
|
INSERT INTO `trx`(`trx_id`) VALUES (1);
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
En el fichero de configuración del complemento establezca el SQL para actualizar la
|
|
tabla del ID de transacciones global usando <literal>on_commit</literal>
|
|
en la sección <literal>global_transaction_id_injection</literal>.
|
|
Asegúrese de que el nombre de la tabla usado para la sentencia <literal>UPDATE</literal>
|
|
está completamente cualificado. En el ejemplo,
|
|
<literal>test.trx</literal> se usa para referirse a la tabla <literal>trx</literal>
|
|
del esquema (base de datos) <literal>test</literal>. Use la tabla que se creó en
|
|
el paso anterior. Es importante establecer el nombre de la tabla completamente cualificado,
|
|
ya que la conexión donde se realiza la inyección puede usar una base de datos predeterminada
|
|
diferente. Asegúrese de que al usuario que abra la conexión
|
|
se le permita ejecutar la sentencia <literal>UPDATE</literal>.
|
|
</para>
|
|
<para>
|
|
Habilite la notificación de los errores que pudieran ocurrir cuando mysqlnd_ms realice inyecciones
|
|
de ID de transacciones global.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Configuración del complemento: SQL para la inyección del GTID en el lado del cliente</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "127.0.0.1",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"global_transaction_id_injection":{
|
|
"on_commit":"UPDATE test.trx SET trx_id = trx_id + 1",
|
|
"report_error":true
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Inyección transparente del ID de transacciones global</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
|
|
if (!$mysqli->query("DROP TABLE IF EXISTS test")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
|
|
if (!$mysqli->query("CREATE TABLE test(id INT)")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
|
|
if (!$mysqli->query("INSERT INTO test(id) VALUES (1)")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* modo autoconsigna, lectura en el esclavo, sin incremento */
|
|
if (!($res = $mysqli->query("SELECT id FROM test"))) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
var_dump($res->fetch_assoc());
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
array(1) {
|
|
["id"]=>
|
|
string(1) "1"
|
|
}
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
El ejemplo ejecuta tres sentencias en el modo autoconsigna en el maestro, causando
|
|
tres transacciones en el maestro. Para cada sentencia, el complemento
|
|
inyectará el <literal>UPDATE</literal> configurado de forma transparente antes de ejecutar
|
|
las sentencias SQL del usuario. Cuando finaliza el script, el contador de IDs
|
|
de transacciones globales en el maestro ha sido incrementado en tres.
|
|
</para>
|
|
<para>
|
|
La cuarta sentencia SQL ejecutada en el ejemplo, un <literal>SELECT</literal>,
|
|
no desencadena un incremento. Solamente las transacciones (escrituras) ejecutadas en un maestro
|
|
incrementarán el contador GTID.
|
|
</para>
|
|
<note>
|
|
<title>SQL para el ID de transacciones global: ¡se busca una solución eficiente!</title>
|
|
<para>
|
|
El SQL usado para la emulación del ID de transacciones global es ineficiente.
|
|
Está optimizado para la trasnparencia, no para el rendimiento. No lo use para entornos de
|
|
producción. Por favor, ayude a encontrar una solución eficiente para su inclusión en el manual.
|
|
Apreciamos su aportación.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
<example>
|
|
<title>Configuración del complemento: SQL para obtener el GTID</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "127.0.0.1",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"global_transaction_id_injection":{
|
|
"on_commit":"UPDATE test.trx SET trx_id = trx_id + 1",
|
|
"fetch_last_gtid" : "SELECT MAX(trx_id) FROM test.trx",
|
|
"report_error":true
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Obtener el GTID después de la ejecución</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
|
|
if (!$mysqli->query("DROP TABLE IF EXISTS test")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
printf("GTID después de la transacción %s\n", mysqlnd_ms_get_last_gtid($mysqli));
|
|
|
|
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
|
|
if (!$mysqli->query("CREATE TABLE test(id INT)")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
printf("GTID después de la transacción %s\n", mysqlnd_ms_get_last_gtid($mysqli));
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
GTID después de la transacción 7
|
|
GTID después de la transacción 8
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Las aplicaciones pueden preguntar a PECL mysqlnd_ms por un ID de transacciones global que
|
|
pertenezca a la última operación de escritura realizada por la aplicación.
|
|
La función <function>mysqlnd_ms_get_last_gtid</function> devuelve el
|
|
GTID obtenido cuando se ejecuta la sentencia SQL desde la
|
|
entrada <literal>fetch_last_gtid</literal> de la sección
|
|
<literal>global_transaction_id_injection</literal> del
|
|
fichero de configuración del complemento. La función puede ser invocada
|
|
después de que el GTID haya sido incrementado.
|
|
</para>
|
|
<para>
|
|
Se aconseja que las aplicaciones no ejecuten la sentencia
|
|
SQL por sí mismas ya que aumenta el riesgo de que accidentalmente se ocasione un incremento
|
|
implícito del GTID. También, si se usa la función, es más fácil migrar
|
|
una aplicación desde una sentencia SQL para obtener un ID de transacciones a otra,
|
|
por ejemplo, si ningún servidor MySQL incluye el soporte interno para IDs de transacciones globales.
|
|
</para>
|
|
<para>
|
|
Esta guía rápida muestra una sentencia SQL que devolverá un GTID igual o mayor
|
|
que el creado por la sentencia anterior. Éste es exactamente el GTID creado
|
|
por la sentencia anterior si ningún cliente ha incrementado el GTID en el
|
|
tiempo transcurrido entre la ejecución de la sentencia y el <literal>SELECT</literal>
|
|
para obtener el GTID. De otro modo, será mayor.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Configuración del complemento: comprobar un GTID en particular</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "127.0.0.1",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"global_transaction_id_injection":{
|
|
"on_commit":"UPDATE test.trx SET trx_id = trx_id + 1",
|
|
"fetch_last_gtid" : "SELECT MAX(trx_id) FROM test.trx",
|
|
"check_for_gtid" : "SELECT trx_id FROM test.trx WHERE trx_id >= #GTID",
|
|
"report_error":true
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Nivel de servicio de consistencia de sesión y GTID combinados</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
|
|
if ( !$mysqli->query("DROP TABLE IF EXISTS test")
|
|
|| !$mysqli->query("CREATE TABLE test(id INT)")
|
|
|| !$mysqli->query("INSERT INTO test(id) VALUES (1)")
|
|
) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* GTID es un identificador para la última escritura */
|
|
$gtid = mysqlnd_ms_get_last_gtid($mysqli);
|
|
|
|
/* Consistencia de sesión (lectura de sus escrituras): intentar leer de los esclavos, no sólo del maestro */
|
|
if (false == mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION, MYSQLND_MS_QOS_OPTION_GTID, $gtid)) {
|
|
die(sprintf("[006] [%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Ejecutar en el maestro o en el esclavo que ha replicado el INSERT */
|
|
if (!($res = $mysqli->query("SELECT id FROM test"))) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
var_dump($res->fetch_assoc());
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Un GTID devuelto por <function>mysqlnd_ms_get_last_gtid</function>
|
|
se puede usar como una opción para el nivel de servicio de consistencia de sesión.
|
|
La consistencia de sesión proporciona la lectura de sus escrituras. La consistencia de sesión
|
|
puede ser solicitada llamando a
|
|
<function>mysqlnd_ms_set_qos</function>.
|
|
En el ejemplo, el complemento ejecutará la sentencia <literal>SELECT</literal>
|
|
en el maestro o en un esclavo que ya ha repliacdo
|
|
el <literal>INSERT</literal> anterior.
|
|
</para>
|
|
<para>
|
|
PECL mysqlnd_ms comprobará de forma transparente cada esclavo configurado, si
|
|
éste ha replicado el <literal>INSERT</literal>, mediante la comprobación de la tabla
|
|
GTID de esclavos. La comprobación se realiza ejecutando la sentencia SQL establecida con la
|
|
opción <literal>check_for_gtid</literal> de la
|
|
sección <literal>global_transaction_id_injection</literal> del
|
|
fichero de configuración del complemento. Por favor, observe que este es un procedimiento
|
|
lento y caro. Las aplicaciones deberían intentar usarlo poco y únicamente
|
|
si la carga de lectura en el maestro es alta.
|
|
</para>
|
|
<para>
|
|
<emphasis role="bold">Uso de la característica del ID de transacciones global en el lado del servidor</emphasis>
|
|
</para>
|
|
<note>
|
|
<title>Soporte del servidor insuficiente en MySQL 5.6</title>
|
|
<para>
|
|
El complemento ha sido derarrollado usando una versión de preproducción de MySQL 5.6.
|
|
Resulta de que todas las versiones de producción de MySQL 5.6 no proporcionan
|
|
clientes con suficiente información para forzar la consistencia de sesión basándose en GTID.
|
|
Por favor, lea la <link linkend="mysqlnd-ms.gtid">sección de conceptos</link>
|
|
para más detalles.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
Desde MySQL 5.6.5-m8, el sistema de Replicación MySQL introduce los IDs de transacciones
|
|
globales. Los identificadores de transacciones son automáticamente generados y
|
|
mantenidos por el servidor. Los usuarios no necesitan ocuparse de ellos.
|
|
No hay necesidad de configurar ninguna tabla de antemano, ni de configurar
|
|
<literal>on_commit</literal>. Ya no es necesaria la emulación en el lado del cliente.
|
|
</para>
|
|
<para>
|
|
Los clientes puede seguir usando los identificadores de transacciones globales para conseguir
|
|
la consistencia de sesión al leer desde esclavos de la Replicación MySQL en algunos casos, ¡pero no en todos!.
|
|
El algoritmo funciona como está descrito arriba. Se deben configurar diferentes sentencias SQL para
|
|
<literal>fetch_last_gtid</literal> y <literal>check_for_gtid</literal>.
|
|
Estas sentencias se proporcionan más abajo. Por favor, observe que MySQL 5.6.5-m8 es una versión en
|
|
desarrollo. Los detalles de la implementación del servidor pueden cambiar en el futuro y requerir
|
|
la adopción de las sentencias SQL mostradas.
|
|
</para>
|
|
<para>
|
|
Al utilizar la siguiente configuración, cualquier funcionalidad descrita arriba puede
|
|
usarse junto con la característica del ID de transacciones global en el lado del servidor.
|
|
<function>mysqlnd_ms_get_last_gtid</function> y <function>mysqlnd_ms_set_qos</function>
|
|
siguen funcionando como se describió arriba. La única diferencia es que el servidor
|
|
no utiliza un número de secuencia simple, sino un string que contiene un identificador del servidor
|
|
y un número de secuencia. Por lo tanto, los usuarios no podrán obtener fáciltmente un orden desde los GTIDs
|
|
devueltos por <function>mysqlnd_ms_get_last_gtid</function>.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Configuración de complemento: usar la característica del GTID interna de MySQL 5.6.5-m8</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "127.0.0.1",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
"global_transaction_id_injection":{
|
|
"fetch_last_gtid" : "SELECT @@GLOBAL.GTID_DONE AS trx_id FROM DUAL",
|
|
"check_for_gtid" : "SELECT GTID_SUBSET('#GTID', @@GLOBAL.GTID_DONE) AS trx_id FROM DUAL",
|
|
"report_error":true
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
</section>
|
|
<section xml:id="mysqlnd-ms.quickstart.cache">
|
|
<title>Integración de una caché</title>
|
|
<note>
|
|
<title>Requisitos de versión, dependencias y estado</title>
|
|
<para>
|
|
Se puede encontrar más sobre los requisitos de la versión, las dependencias del orden de carga de las extensiones y
|
|
el estado actual en la <link linkend="mysqlnd-ms.concept_cache">sección de conceptos</link>.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
Los clústeres de bases de datos pueden proporcionar diferentes niveles de consistencia. A partir de
|
|
<literal>PECL/mysqlnd_ms</literal> 1.2.0 es posible acosejar al complemento para que cosidere solamente los
|
|
nodos del clúster que puedan proporcionar el nivel de consistencia requerido. Por ejemplo,
|
|
si se usa la Replicación MySQL con su consistencia final a lo ancho del
|
|
clúster, es posible solicitar la consistencia de sesión (lectura de sus escrituras)
|
|
en cualquier momento usando <function>mysqlnd_ms_set_quos</function>. Por favor, vea también la
|
|
introducción de
|
|
<link linkend="mysqlnd-ms.quickstart.qos-consistency">nivel de servicio y consistencia</link>.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Resumen: calidad del servicio para solicitar la lectura de sus escrituras</title>
|
|
<programlisting role="php">
|
|
/* Solicitud de consistencia de sesión: lectura de sus escrituras */
|
|
if (!mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION))
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Asumiendo que a PECL/mysqlnd se le ha indicado explícitamente que proporcione un nivel de consistencia
|
|
no mayor que la consistencia final, es posible reemplazar un acceso de lectura de un nodo de
|
|
la base de datos por una caché en el lado del cliente usando el tiempo de vida (TTL) como
|
|
estrategia de invalidación. El nodo de la base de datos y la caché pueden o no servir
|
|
datos actuales, ya que esto es lo que la consistencia final define.
|
|
</para>
|
|
<para>
|
|
El reemplazo de un acceso de lectura de un nodo de la base de datos por un acceso a la caché local
|
|
puede mejorar el redimiento general y disminuir la carga de la base de datos. Si la entrada de la caché
|
|
es reutilizada por otro cliente que no sea el que creó dicha entrada,
|
|
se guaradará un acceso a la base de datos, por lo que se disminuye la carga de la base de datos. Además,
|
|
el rendimiento del sistema se puede mejorar si el cálculo y la entrega
|
|
de una consulta a la base de datos es más baja que un acceso a la caché local.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Configuración del complemento: sin entradas especiales para la caché</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "127.0.0.1",
|
|
"port": "3306"
|
|
}
|
|
},
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Almacenar en caché una petición a un servidor</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
if ( !$mysqli->query("DROP TABLE IF EXISTS test")
|
|
|| !$mysqli->query("CREATE TABLE test(id INT)")
|
|
|| !$mysqli->query("INSERT INTO test(id) VALUES (1)")
|
|
) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Explícitamente permite la consistencia final y el almacenamiento en caché (TTL <= 60 segundos) */
|
|
if (false == mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL, MYSQLND_MS_QOS_OPTION_CACHE, 60)) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Para que este ejemplo funcione, debemos esperar a que el esclavo se ponga al día. Estilo de fuerza bruta. */
|
|
$intentos = 0;
|
|
do {
|
|
/* comprobar si el esclavo posee la tabla */
|
|
if ($res = $mysqli->query("SELECT id FROM test")) {
|
|
break;
|
|
} else if ($mysqli->errno) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
/* esperar a que el esclavo se ponga al día */
|
|
usleep(200000);
|
|
} while ($intentos++ < 10);
|
|
|
|
/* La consulta ha sido ejecutada en un esclavo, el resultado está en la caché */
|
|
assert($res);
|
|
var_dump($res->fetch_assoc());
|
|
|
|
/* Servido desde la caché */
|
|
$res = $mysqli->query("SELECT id FROM test");
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
El ejemplo muestra cómo usar la caché. Primero, se ha de establecer
|
|
la calidad del servicio al de consistencia final y tener en cuenta explícitamente el almacenamiento en la caché.
|
|
Esto se lleva a cabo llamando a <function>mysqlnd_ms_set_qos</function>.
|
|
Luego, el conjunto de resultados de cada sentencia de solo lectura se almacena en la caché hasta un
|
|
máximo de segundos según lo permitido por <function>mysqlnd_ms_set_qos</function>.
|
|
</para>
|
|
<para>
|
|
El TTL real es menor o igual al valor establecido
|
|
con <function>mysqlnd_ms_set_qos</function>. El valor pasado a la función
|
|
establece la edad máxima (en segundos) de los datos proporcionados. Para calcular
|
|
el valor real del TTL, se comprueba la demora de replicación de un servidor y se resta
|
|
del valor dado. Si, por ejemplo, la edad máxima es de 60 segundos y
|
|
el esclavo notifica una demora de 10 segundos, el TTL resultante es de 50 segundos.
|
|
El TTL se calcula de forma individual para cada consulta de la caché.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Lectura de sus escrituras y almacenamiento en la caché combinados</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
if ( !$mysqli->query("DROP TABLE IF EXISTS test")
|
|
|| !$mysqli->query("CREATE TABLE test(id INT)")
|
|
|| !$mysqli->query("INSERT INTO test(id) VALUES (1)")
|
|
) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Explícitamente permite la consistencia final y el almacenamiento en caché (TTL <= 60 segundos) */
|
|
if (false == mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_EVENTUAL, MYSQLND_MS_QOS_OPTION_CACHE, 60)) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Para que este ejemplo funcione, debemos esperar a que el esclavo se ponga al día. Estilo de fuerza bruta. */
|
|
$intentos = 0;
|
|
do {
|
|
/* comprobar si el esclavo posee la tabla */
|
|
if ($res = $mysqli->query("SELECT id FROM test")) {
|
|
break;
|
|
} else if ($mysqli->errno) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
/* esperar a que el esclavo se ponga al día */
|
|
usleep(200000);
|
|
} while ($intentos++ < 10);
|
|
|
|
assert($res);
|
|
|
|
/* La consulta ha sido ejecutada en un esclavo, el resultado está en la caché */
|
|
var_dump($res->fetch_assoc());
|
|
|
|
/* Servido desde la caché */
|
|
if (!($res = $mysqli->query("SELECT id FROM test"))) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
var_dump($res->fetch_assoc());
|
|
|
|
/* Actualización en el maestro */
|
|
if (!$mysqli->query("UPDATE test SET id = 2")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Lectura de sus escrituras */
|
|
if (false == mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION)) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Obtener los últimos datos */
|
|
if (!($res = $mysqli->query("SELECT id FROM test"))) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
var_dump($res->fetch_assoc());
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
La calidad del servicio se puede cambiar en cualquier momento para evitar el uso de la caché.
|
|
Si fuera necesario, se podría cambiar para leer sus escrituras (consistencia de sesión). En este caso,
|
|
la caché no se usará y se leerán datos actuales.
|
|
</para>
|
|
</section>
|
|
<section xml:id="mysqlnd-ms.quickstart.failover">
|
|
<title>Tolerancia a fallos</title>
|
|
<para>
|
|
Por omisión, el complemento no intenta la tolerancia a fallos si falla al conectarse a un equipo
|
|
anfitrión. Esto evita los problemas relacionados con el
|
|
<link linkend="mysqlnd-ms.quickstart.connectionpooling">estado de la conexión</link>.
|
|
Se recomienda tratar de forma manual los errores de conexión de una manera similar a una transacción
|
|
fallida. Se debería capturar el error, reconstruir el estado de la conexión y volver a ejecutar la
|
|
consulta tal como se muestra abajo.
|
|
</para>
|
|
<para>
|
|
Si el estado de la conexión no es la cuestión, de forma alternativa se puede habilitar la tolerancia
|
|
a fallos automática y silenciosa. Dependiendo de la configuración, la tolerancia a fallos automática
|
|
y silenciosa se intentará en el maestro antes de emitir un error, o se intentará
|
|
conectar a otros esclavos, dada la consulta permitida para ello, antes de intentar conectarse
|
|
a un maestro. Ya que la <link linkend="mysqlnd-ms.failover">tolerancia a fallos automática</link> no
|
|
es infalible, no se trata en esta guía rápida. En su lugar, los detalles se proporcionan
|
|
en la sección de conceptos posterior.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Tolerancia a fallos manual, opcional automática</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "simulate_slave_failure",
|
|
"port": "0"
|
|
},
|
|
"slave_1": {
|
|
"host": "127.0.0.1",
|
|
"port": 3311
|
|
}
|
|
},
|
|
"filters": { "roundrobin": [] }
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Tolerancia a fallos manual</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "nombre_usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
$sql = "SELECT 1 FROM DUAL";
|
|
|
|
/* el manejo de errores debería realizarse sin tener en cuenta el complemento */
|
|
if (!($res = $enlace->query($sql))) {
|
|
/* específico del complemento: comprobar errores de conexión */
|
|
switch ($enlace->errno) {
|
|
case 2002:
|
|
case 2003:
|
|
case 2005:
|
|
printf("Error de conexión - ¡intentando con el siguiente esclavo!\n");
|
|
/* el equilibrador de carga elegirá el siguiente esclavo */
|
|
$res = $enlace->query($sql);
|
|
break;
|
|
default:
|
|
/* no hay errores de conexión, la tolerancia a fallos es poco probable que ayude */
|
|
die(sprintf("SQL error: [%d] %s", $enlace->errno, $enlace->error));
|
|
break;
|
|
}
|
|
}
|
|
if ($res) {
|
|
var_dump($res->fetch_assoc());
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
</section>
|
|
<section xml:id="mysqlnd-ms.quickstart.partitioning">
|
|
<title>Particionamiento y fragmentación</title>
|
|
<para>
|
|
La agrupación (clustering) de bases de datos se realiza por varias razones. Los clúster pueden mejorar la disponibilidad,
|
|
la tolerancia a fallos, e incrementar el rendimiento aplicando un enfoque de "divide y vencerás",
|
|
ya que el trabajo se distribuye en varias máquinas. La agrupación (clustering) a veces se combina con
|
|
particionamiento y fragmentación para dividir aún más una tarea compleja grande en unidades
|
|
más pequeñas y manejables.
|
|
</para>
|
|
<para>
|
|
El complemento mysqlnd_ms pretende dar soporte a una gran variedad de clústeres de bases de datos de MySQL. Algunos sabores de
|
|
clúster de bases de datos de MySQL poseen métodos internos para el particionamiento y la fragmentacion,
|
|
los cuales podrían ser transparentes de usar. El complemetno admite los dos enfoques más
|
|
comunes: filtración de tabla de Replicación de MySQL, y la fragmentación
|
|
(particionamiento basada en aplicación).
|
|
</para>
|
|
<para>
|
|
La Replicación de MySQL admite el particionamiento como filtros que permiten
|
|
crear esclavos que replican todas las bases de datos o específicas del maestro, o tablas.
|
|
Es entonces responsabilidad de la aplicación
|
|
elegir un esclavo según las reglas de los filtros. Se puede usar el filtro
|
|
<literal><link linkend="ini.mysqlnd-ms-plugin-config-v2.filter-node-groups">node_groups</link></literal>
|
|
de mysqlnd_ms para dar soporte manual a esto, o usar el filtro de tablas experimental.
|
|
</para>
|
|
<para>
|
|
El particionamiento manual o fragmentación está soportado a través del
|
|
filtro de agrupación de nodos, y de las sugerencias SQL a partir de 1.5.0. El filtro node_groups
|
|
permite asignar un nombre simbólico a un grupo de servidores maestros y esclavos.
|
|
En el ejemplo, el maestro <literal>master_0</literal> y <literal>slave_0</literal>
|
|
forman un grupo con el nombre de <literal>Partition_A</literal>. Es su responsabilidad
|
|
decidir lo que haga un grupo. Por ejemplo, se podrían usar grupos de
|
|
nodos para la fragmentación, y usar los nombres de grupos para direccionar fragmentos
|
|
como <literal>Shard_A_Range_0_100</literal>.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Clúster de grupos de nodos</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"master": {
|
|
"master_0": {
|
|
"host": "localhost",
|
|
"socket": "\/tmp\/mysql.sock"
|
|
}
|
|
},
|
|
"slave": {
|
|
"slave_0": {
|
|
"host": "simulate_slave_failure",
|
|
"port": "0"
|
|
},
|
|
"slave_1": {
|
|
"host": "127.0.0.1",
|
|
"port": 3311
|
|
}
|
|
},
|
|
"filters": {
|
|
"node_groups": {
|
|
"Partition_A" : {
|
|
"master": ["master_0"],
|
|
"slave": ["slave_0"]
|
|
}
|
|
},
|
|
"roundrobin": []
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Particionamiento manual usando sugerencias SQL</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function select($mysqli, $msj, $sugerencia = '')
|
|
{
|
|
/* Nota: prueba débil, dos conexiones a dos servidores podrían tener el mismo id de hilo */
|
|
$sql = sprintf("SELECT CONNECTION_ID() AS _thread, '%s' AS _hint FROM DUAL", $msj);
|
|
if ($sugerencia) {
|
|
$sql = $sugerencia . $sql;
|
|
}
|
|
if (!($res = $mysqli->query($sql))) {
|
|
printf("[%d] %s", $mysqli->errno, $mysqli->error);
|
|
return false;
|
|
}
|
|
$fila = $res->fetch_assoc();
|
|
printf("%d - %s - %s\n", $fila['_thread'], $fila['_hint'], $sql);
|
|
return true;
|
|
}
|
|
|
|
$mysqli = new mysqli("myapp", "usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* Permitir siempre esclavos */
|
|
select($mysqli, "slave_0");
|
|
select($mysqli, "slave_1");
|
|
|
|
/* solamente permtir los servidores del grupo de nodos "Partition_A" */
|
|
select($mysqli, "slave_1", "/*Partition_A*/");
|
|
select($mysqli, "slave_1", "/*Partition_A*/");
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
<screen>
|
|
<![CDATA[
|
|
6804 - slave_0 - SELECT CONNECTION_ID() AS _thread, 'slave1' AS _hint FROM DUAL
|
|
2442 - slave_1 - SELECT CONNECTION_ID() AS _thread, 'slave2' AS _hint FROM DUAL
|
|
6804 - slave_0 - /*Partition_A*/SELECT CONNECTION_ID() AS _thread, 'slave1' AS _hint FROM DUAL
|
|
6804 - slave_0 - /*Partition_A*/SELECT CONNECTION_ID() AS _thread, 'slave1' AS _hint FROM DUAL
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Por omisión, el complemento usará todos los servidores maestros y esclavos configurados para
|
|
la ejecución de consultas. Pero si una consulta comienza con una sugerencia SQL como
|
|
<literal>/*node_group*/</literal>, el complemento considerará únicamente los servidores
|
|
enumerados en <literal>node_group</literal> para la ejecución de consultas. Así,
|
|
las consultas <literal>SELECT</literal> prefijadas con <literal>/*Partition_A*/</literal>
|
|
úncamente serán ejecutadas en <literal>slave_0</literal>.
|
|
</para>
|
|
|
|
</section>
|
|
|
|
|
|
<section xml:id="mysqlnd-ms.quickstart.mysql_fabric">
|
|
<title>MySQL Fabric</title>
|
|
<note>
|
|
<title>Requisitos de versión y estado</title>
|
|
<para>
|
|
Se comenzó a trabajar en el soporte para MySQL Fabricen la versión 1.6.
|
|
Considere el soporte con una calidad prealfa. El manual podría no enumerar
|
|
todas las limitaciones de la/s característica/s. Aún se está trabajand en ello.
|
|
</para>
|
|
<para>
|
|
La fragmentación es el único caso de uso soportado por el complemento hasta la fecha.
|
|
</para>
|
|
</note>
|
|
<note>
|
|
<title>Conceptos de MySQL Fabric</title>
|
|
<para>
|
|
Por favor, verfique el manual de referencia de MySQL para más información sobre MySQL Fabric
|
|
y cómo configurarlo. El manual de PHP asumen que se es familiar
|
|
con los conceptos e ideas básicos de MySQL Fabric.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
MySQL Fabric es un sistema para gestionar granjas de servidores MySQL para lograr una
|
|
Alta Disponibilidad y opcionalmente soporte para fragmentación. Técnicamente, es un
|
|
middleware para gestionar y monitorizar servidores MySQL.
|
|
</para>
|
|
<para>
|
|
Los clientes consultan a MySQL Fabric para obtener listas de servidores MySQL,
|
|
sus estados y sus roles. Por ejemplo, los clientes pueden solicitar una lista de
|
|
esclavos para un grupo de Replicacion MySQL y para ver si están listos para
|
|
manejar peticiones SQL. Otro ejemplo es un clúster de servidores de MySQL fragmentados
|
|
donde el cliente busca conocer a qué fragmento consultar para una tabla y clave de
|
|
de fragmentación dados. Si se configura para utilizar Fabric, el complemento usa XML RCP sobre HTTP
|
|
para obtener la lista en tiempo de ejecución desde un host deto MySQL Fabric. La llamada XML a un
|
|
procedimiento remoto se realiza en segundo plano y de forma transparente desde el punto
|
|
de vista de los desarrolladores.
|
|
</para>
|
|
<para>
|
|
En lugar de enumerar los servidores MySQL directamente en el fichero de configuración del
|
|
complemento, este contiene una lista de uno o más host de MySQL Fabric.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Configuración del complemento: hosts de Fabric en lugar de servidores MySQL</title>
|
|
<programlisting role="ini">
|
|
<![CDATA[
|
|
{
|
|
"myapp": {
|
|
"fabric": {
|
|
"hosts": [
|
|
{
|
|
"host" : "127.0.0.1",
|
|
"port" : 8080
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Los usuarios utilizan las nuevas funciones
|
|
<function>mysqlnd_ms_fabric_select_shard</function> y
|
|
<function>mysqlnd_ms_fabric_select_global</function> para cambiar al
|
|
conjunto de servidores responsables de una clave de fragmento dada. Luego, el
|
|
complemento elige un servidor apropiado para ejecutar las consultas.
|
|
Al hacer esto, el complemento se encarga del conjunto de reglas adicionales
|
|
de equilibrado de carga.
|
|
</para>
|
|
<para>
|
|
El ejemplo de abajo asume que MySQL Fabric ha sido configurado
|
|
al fragmento de tabla <literal>test.fabrictest</literal> usando
|
|
la columna <literal>id</literal> de la tabla como clave de fragmento.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Particionameinto manual usando sugerencias SQL</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$mysqli = new mysqli("myapp", "usuario", "contraseña", "base_datos");
|
|
if (!$mysqli) {
|
|
/* Por supuesto, su manejo de errores es mejor... */
|
|
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
|
|
}
|
|
|
|
/* Crear una tabla global - una tabla disponible en todos los fragmentos */
|
|
mysqlnd_ms_fabric_select_global($mysqli, "test.fabrictest");
|
|
if (!$mysqli->query("CREATE TABLE test.fabrictest(id INT NOT NULL PRIMARY KEY)")) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Cambiar la conexión al fragmento apropiado e insertar un registro */
|
|
mysqlnd_ms_fabric_select_shard($mysqli, "test.fabrictest", 10);
|
|
if (!($res = $mysqli->query("INSERT INTO fabrictest(id) VALUES (10)"))) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
|
|
/* Intentar leer el recién insertado registro */
|
|
mysqlnd_ms_fabric_select_shard($mysqli, "test.fabrictest", 10);
|
|
if (!($res = $mysqli->query("SELECT id FROM test WHERE id = 10"))) {
|
|
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
El ejemplo crea una tabla fragmentada, inserta un registro y lo lee
|
|
después. Todas las operaciones de lenguaje de definición de datos (DDL)
|
|
sobre una tabla fragmentada deben aplicarse a lo que se llama el grupo de servidores global.
|
|
Antes de crear o alterar una tabla fragmentada, se llama a
|
|
<function>mysqlnd_ms_fabric_select_global</function>
|
|
para cambiar la conexión dada a los servidores correspondientes del grupo
|
|
global. Las senetencias SQL de manipulación de datos (DML) deben ser enviadas a los fragmentos
|
|
directamente. La función <link linkend="function.mysqlnd-ms-fabric-select-shard">
|
|
<function>mysqlnd_ms_fabric_select_shard</function></link> cambia una
|
|
conexión para manejar el fragmento de una cierta clave de fragmento.
|
|
</para>
|
|
|
|
|
|
</section>
|
|
</chapter>
|
|
<!-- Keep this comment at the end of the file
|
|
Local variables:
|
|
mode: sgml
|
|
sgml-omittag:t
|
|
sgml-shorttag:t
|
|
sgml-minimize-attributes:nil
|
|
sgml-always-quote-attributes:t
|
|
sgml-indent-step:1
|
|
sgml-indent-data:t
|
|
indent-tabs-mode:nil
|
|
sgml-parent-document:nil
|
|
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
|
sgml-exposed-tags:nil
|
|
sgml-local-catalogs:nil
|
|
sgml-local-ecat-files:nil
|
|
End:
|
|
vim600: syn=xml fen fdm=syntax fdl=2 si
|
|
vim: et tw=78 syn=sgml
|
|
vi: ts=1 sw=1
|
|
-->
|