1
0
mirror of https://github.com/php/doc-es.git synced 2026-03-26 08:22:08 +01:00
Files
archived-doc-es/reference/mysqli/quickstart.xml
Pedro Antonio Gil Rodríguez 210ea55fd9 Correcciones menores de otros usuarios
git-svn-id: https://svn.php.net/repository/phpdoc/es/trunk@337301 c90b9560-bf6c-de11-be94-00142212c4b1
2015-08-06 10:13:46 +00:00

1814 lines
62 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 220e2c2d26f47e536bf3cc53ac38547a6ad86c38 Maintainer: seros Status: ready -->
<!-- Reviewed: no Maintainer: seros -->
<chapter xml:id="mysqli.quickstart" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Guía rápida</title>
<para>
Esta guía rápida le ayudará a elegir y obtener familiaridad
con la API MySQL de PHP.
</para>
<para>
Esta guía rápida ofrece información general sobre la extensión mysqli. Se proporcionan
ejemplos de código para los aspectos más importantes de la API. Los conceptos de la base de datos se explican
con el grado necesario para presentar conceptos específicos de MySQL.
</para>
<para>
Se requiere: Familiaridad con el lenguaje de programación PHP, con el lenguaje SQL,
y conociemientos básicos del servidor MySQL.
</para>
<section xml:id="mysqli.quickstart.dual-interface">
<title>Interfaz dual: procedimental y orientada a objetos</title>
<para>
La extensión mysqli ofrece una interfaz dual. Soporta el paradigme de programación
procedimental y el orientado a objetos.
</para>
<para>
Los usuarios que migren desde la extensión mysql antigua pueden preferir la interfaz
procedimental. Esta interfaz es similar a la de la extensión antigua de
mysql. En la mayoría de los casos, los nombres de funciones difieren únicamente por el prefijo.
Algunas funciones de mysqli toman como primer argumento un gestor de conexión,
mientras que las funciones similares de la antigua interfaz de mysql lo
toman como el último argumento opcional.
</para>
<para>
<example>
<title>Migración sencilla desde la antigua extensión mysql</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = mysqli_connect("ejemplo.com", "usuario", "contraseña", "basedatos");
$resultado = mysqli_query($mysqli, "SELECT 'Por favor, no use ' AS _msg FROM DUAL");
$fila = mysqli_fetch_assoc($resultado);
echo $fila['_msg'];
$mysql = mysql_connect("ejemplo.com", "usuario", "contraseña");
mysql_select_db("test");
$resultado = mysql_query("SELECT 'la extensión mysql para nuevos desarrollos.' AS _msg FROM DUAL", $mysql);
$fila = mysql_fetch_assoc($resultado);
echo $fila['_msg'];
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Por favor, no use la extensión mysql para nuevos desarrollos.
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">La interfaz orientada a objetos</emphasis>
</para>
<para>
Además de la clásica interfaz procedimental, los usuarios pueden optar por usar
la interfaz orientada a objetos. La documentación está organizada según
la interfaz orientada a objetos. Esta interfaz muestra las funciones
agrupadas por su propósito, haciendo más fácil los comienzos. La sección de referencia
proporciona ejemplos para ambas variantes de sintaxis.
</para>
<para>
No existen diferencias significativas de rendimiento entre las dos interfaces.
Los usuarios puede basar su elección en sus preferencias personales.
</para>
<para>
<example>
<title>Interfaz orientada a objetos y procedimental</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = mysqli_connect("ejemplo.com", "usuario", "contraseña", "basedatos");
if (mysqli_connect_errno($mysqli)) {
echo "Fallo al conectar a MySQL: " . mysqli_connect_error();
}
$resultado = mysqli_query($mysqli, "SELECT 'Un mundo lleno de ' AS _msg FROM DUAL");
$fila = mysqli_fetch_assoc($resultado);
echo $fila['_msg'];
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Fallo al conectar a MySQL: " . $mysqli->connect_error;
}
$resultado = $mysqli->query("SELECT 'elecciones para complacer a todos.' AS _msg FROM DUAL");
$fila = $resultado->fetch_assoc();
echo $fila['_msg'];
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Un mundo lleno de elecciones para complacer a todos.
]]>
</screen>
</example>
</para>
<para>
Se usa la interfaz orientada a objetos en el inicio rápido porque la
sección de referencia está organizada de esta manera.
</para>
<para>
<emphasis role="bold">Mezclar estilos</emphasis>
</para>
<para>
Es posible cambiar entre los estilos en cualquier momento. No se recomienda mezclar
los dos estilos por razones de claridad y estilo de código.
</para>
<para>
<example>
<title>Estilo de codificación malo</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Fallo al conectar a MySQL: " . $mysqli->connect_error;
}
$resultado = mysqli_query($mysqli, "SELECT 'Estilo malo pero posible.' AS _msg FROM DUAL");
if (!$resultado) {
echo "Fallo al ejecutar la consulta: (" . $mysqli->errno . ") " . $mysqli->error;
}
if ($fila = $resultado->fetch_assoc()) {
echo $fila['_msg'];
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Estilo malo pero posible.
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Véase también</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli_result::fetch_assoc</methodname></member>
<member><link linkend="mysqli.connect-errno">$mysqli::connect_errno</link></member>
<member><link linkend="mysqli.connect-error">$mysqli::connect_error</link></member>
<member><link linkend="mysqli.errno">$mysqli::errno</link></member>
<member><link linkend="mysqli.error">$mysqli::error</link></member>
<member><link linkend="mysqli.summary">El resumen de funciones de la extensión MySQLi</link></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.connections">
<title>Conexiones</title>
<para>
El servidor MySQL soporta el uso de diferentes capas de
transporte para conexiones. Las conexiones usan TCP/IP, sockets de dominio Unix o
tuberías con nombre de Windows.
</para>
<para>
El nombre del host <literal>localhost</literal> tiene un significado especial.
Está vinculado al uso de sockets de dominio Unix. No es posible
abrir una conexión TCP/IP usando como nombre de host <literal>localhost</literal>,
se debe usar <literal>127.0.0.1</literal> en su lugar.
</para>
<para>
<example>
<title>Significado especia de localhost</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("localhost", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Fallo al conectar a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
echo $mysqli->host_info . "\n";
$mysqli = new mysqli("127.0.0.1", "usuario", "contraseña", "basedatos", 3306);
if ($mysqli->connect_errno) {
echo "Fallo al conectar a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
echo $mysqli->host_info . "\n";
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Localhost via UNIX socket
127.0.0.1 via TCP/IP
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Valores predeterminados de los parámetros de conexión</emphasis>
</para>
<para>
Dependiendo de la función de conexión usada se pueden omitir
varios parámetros. Si no se proporciona un parámetro, la extensión intentará
usar los valores predeterminados que están establecidos en el fichero de configuración de PHP.
</para>
<para>
<example>
<title>Configuración predeterminada</title>
<programlisting role="ini">
<![CDATA[
mysqli.default_host=192.168.2.27
mysqli.default_user=root
mysqli.default_pw=""
mysqli.default_port=3306
mysqli.default_socket=/tmp/mysql.sock
]]>
</programlisting>
</example>
</para>
<para>
Los valores resultantes de los parámetros son pasados a la biblioteca cliente
que esté usando esta extensión. Si la biblioteca cliente detecta parámetros vacíos o no
establecidos, puede usar los valores internos predeterminados de la biblioteca.
</para>
<para>
<emphasis role="bold">Valores predeterminados internos de conexión de la biblioteca</emphasis>
</para>
<para>
Si el valor del host no está establecido o está vacío, la biblioteca cliente
usará una conexión de socket Unix sobre <literal>localhost</literal>.
Si el socket no está establecido o está vacío, y es solicitada una conexión de socket Unix,
se intentará una conexiónal socket predeterminado de
<literal>/tmp/mysql.sock</literal>.
</para>
<para>
En sistemas Windows, el nombre de host <literal>.</literal> es interpretado
por la biblioteca cliente como un intento de abrir una conexión basada en una tubería con nombre
de Windows. En este caso el parámetro del socket se interpreta como el nombre de la
tubería. Si no se proporciona o está vacío, se usará como socket (nombre de la tubería)
<literal>\\.\pipe\MySQL</literal>.
</para>
<para>
Si no está establecida una conexión basada en un socket de dominio Unix ni en una
tubería con nombre de Windows y el valor del parámetro del puerto no está establecido, la biblioteca
usuará como puerto predeterminado el <literal>3306</literal>.
</para>
<para>
La biblioteca <link linkend="mysqlnd.overview">mysqlnd</link> y la
Biblioteca Cliente de MySQL (libmysqlclient) implementan la misma lógica para determinados valores predeterminados.
</para>
<para>
<emphasis role="bold">Opciones de conexión</emphasis>
</para>
<para>
Las opciones de conexión están disponibles para, por ejemplo, establecer
comando iniciales que son ejecutados sobre la conexión, o para solicitar el uso de
ciertos conjuntos de caracteres. Las opciones de conexión deben ser establecidas antes de que se
establezcla una conexión de red.
</para>
<para>
Para configurar una opción de conexión, la operación de conexión ha de ser
realizada en tres pasos: crear un gestor de conexión con
<function>mysqli_init</function>, establecer las opciones solicitadas usando
<function>mysqli_options</function>, y establecer la conexión de red
con <function>mysqli_real_connect</function>.
</para>
<para>
<emphasis role="bold">Caché de conexiones</emphasis>
</para>
<para>
La extensión mysqli soporta conexiones persistentes a bases de datos, las cuales
son un tipo especial de conexiones almacenadas en caché. Por defecto, cada conexión
a una base de datos abierta por un script es cerrada explícitamente por el usuario durante
el tiempo de ejecución o liberada automáticamente al finalizar el script. Una conexión
persistente no. En su lugar, se coloca en una caché para su reutilización posterior, si
una conexión es abierta al mismo servidor usando el mismo nombre de usuario, contraseña, socket, puerto
y base de datos predeterminada. La reutilización ahorra gastos de conexioń.
</para>
<para>
Cada procesos de PHP utiliza su propia caché de conexiones mysqli.
Dependiendo de modelo de distribución del servidor web, un proceso PHP puede servir
una o múltiples peticiones. Por lo tanto, una conexión almacenada en caché puede ser
utilizada posteriormente por uno o más scripts.
</para>
<para>
<emphasis role="bold">Conexiones persistentes</emphasis>
</para>
<para>
Si una conexión persistente no usada con una combiación dada de host, nombre de usuario,
contraseña, socket, puerto y base de datos predeterminada no se puede encontrar en la caché de conexiones,
mysqli abrirá una nueva conexión. El uso de conexiones persistentes se puede
habilitar y deshabilitar usando la directiva de PHP <link linkend="ini.mysqli.allow-persistent">mysqli.allow_persistent</link>.
El número total de conexiones abiertas por un script puede ser limitado con
<link linkend="ini.mysqli.max-links">mysqli.max_links</link>. El número máximo de conexiones persistentes
por proceso de PHP puede restringirse con <link linkend="ini.mysqli.max-persistent">mysqli.max_persistent</link>.
Observe que el servidor web puede engendrar muchos procesos de PHP.
</para>
<para>
Una queja común sobre las conexiones persistentes qes que su estado no
es reiniciado antes de su uso. Por ejemplo, las transacciones abiertas y no finalizadas no son
automéáticamente reanudadas. También, los cambios de autorización que ocurran
durante la colocación de la conexión en la caché y su reutilización
no están reflejados. Esto puede verse como un efecto secundario no deseado. Al contrario,
el nombre <literal>persistente</literal> puede entenderse como una promesa
de que el estado persiste.
</para>
<para>
La extensión mysqli soporta ambas interpretaciones de una conexión persistente:
el estado persiste, y el estado se reinicia antes de la reutilización. Lo predeterminado es la reiniciación.
Antes de que una conexión sea reutilizada, la extensión llama
implicitamente a <function>mysqli_change_user</function> para reiniciar el estado. La
conexión persistente aparece al usuario como si estuviera recién abierta. No
son visibles ningún artefacto de los usos previos.
</para>
<para>
La función <function>mysqli_change_user</function> es una operación cara.
Para un mejor rendimiento, los usuarios pueden recompilar la extensión con la
bandera de compilación <constant>MYSQLI_NO_CHANGE_USER_ON_PCONNECT</constant> establecida.
</para>
<para>
Corresponde al usuario elegir entre comportamiento seguro o mejor rendimiento.
Ambas son metas de optimización válidas. Para facilitar el uso, el comportamiento seguro
es el predeterminado a expensas de un rendimiento máximo.
</para>
<para>
<emphasis role="bold">See also</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::init</methodname></member>
<member><methodname>mysqli::options</methodname></member>
<member><methodname>mysqli::real_connect</methodname></member>
<member><methodname>mysqli::change_user</methodname></member>
<member><link linkend="mysqli.get-host-info">$mysqli::host_info</link></member>
<member><link linkend="mysqli.configuration">Opciones de configuración de MySQLi</link></member>
<member><link linkend="features.persistent-connections">Conexiones persistentes a bases de datos</link></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.statements">
<title>Ejecutar sentencias</title>
<para>
Las sentencias se pueden ejecutar con las funciones
<function>mysqli_query</function>, <function>mysqli_real_query</function>
y <function>mysqli_multi_query</function>.
La función <function>mysqli_query</function> es la más
común, y combina la sentencia de ejecucución con su
conjunto de resultados obtenido de un buffer, si lo hubiera, en una llamada.
Llamar a <function>mysqli_query</function> es idéntico que
llamar a <function>mysqli_real_query</function>
seguido de <function>mysqli_store_result</function>.
</para>
<para>
<example>
<title>Conectando a MySQL</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión con MySQL: (" . $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)")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
?>
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Conjuntos de resultados almacenados en buffer</emphasis>
</para>
<para>
Después de la ejecución de sentencias, los resultados pueden recuperarse de una sola vez para que sean almacenados en buffer
por el cliente o leyendo fila a fila. El almacenamieno en buffer de conjuntos de resultados en el lado del cliente
permite al servidor liberar recursos asociados con los resultados de la
sentencia tan pronto como sea posible. Generalmente hablando, los clientes son lentos
consumiendo conjuntos de resultados. Por lo tanto, se recomienda usar conjuntos de resultados
almacenados en buffer. <function>mysqli_query</function> combina la ejecución de
sentencias y el almacenamiento en buffer de conjuntos de resultados.
</para>
<para>
Las aplicaciones de PHP pueden navegar libremente a través de resultados almacenados en buffer.
La navegación es más rápida debido a que los conjuntos de resultados se mantienen en la memoria del cliente.
Tenga en cuenta que a menudo es más fácil procesar datos en el cliente que
hacerlo el servidor.
</para>
<para>
<example>
<title>Navegación a través de resultados almacenados en buffer</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $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), (2), (3)")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
$resultado = $mysqli->query("SELECT id FROM test ORDER BY id ASC");
echo "Orden inverso...\n";
for ($num_fila = $resultado->num_rows - 1; $num_fila >= 0; $num_fila--) {
$resultado->data_seek($num_fila);
$fila = $resultado->fetch_assoc();
echo " id = " . $fila['id'] . "\n";
}
echo "Orden del conjunto de resultados...\n";
$resultado->data_seek(0);
while ($fila = $resultado->fetch_assoc()) {
echo " id = " . $fila['id'] . "\n";
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Orden inverso...
id = 3
id = 2
id = 1
Orden del conjunto de resultados...
id = 1
id = 2
id = 3
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Conjuntos de resultados no almacenados en buffer</emphasis>
</para>
<para>
Si la memoria del cliente es un recurso escaso y no es necesaria la liberación de recursos
del servidor tan pronto como sea posible para mantener la carga del servidor baja,
se pueden usar conjuntos de resultados no almacenados en buffer. Recorrer resultados no almacenados en buffer
no es posible antes de que todas las filas hayan sido leídas.
</para>
<para>
<example>
<title>Navegación a través de resultados no almacenados en buffer</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli->real_query("SELECT id FROM test ORDER BY id ASC");
$resultado = $mysqli->use_result();
echo "Orden del conjunto de resultados...\n";
while ($fila = $resultado->fetch_assoc()) {
echo " id = " . $fila['id'] . "\n";
}
?>
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Tipos de datos de los valores del conjunto de resultados</emphasis>
</para>
<para>
Las funciones <function>mysqli_query</function>, <function>mysqli_real_query</function>
y <function>mysqli_multi_query</function> se usan para ejecutar
sentencias no preparadas. Al nivel del Protocolo Cliente Servidor de MySQL,
el comando <literal>COM_QUERY</literal> y el protocolo de texto se usan
para la ejecución de sentencias. Con el protocolo texto, el servidor MySQL convierte
todos los datos de los conjuntos de resultados en cadenas antes de enviarlos. Esta conversión se realiza
sin considerar el tipo de datos de las columnas del conjunto de resultados SQL. Las bibliotecas cliente de mysql
reciben todos los valores de las columnas como cadenas. No se realiza ninguna conversión del lado del cliente
pava volver a convertir las columnas a susu tipos nativos. En su lugar, todos los valores son
proporcionados como cadenas de PHP.
</para>
<para>
<example>
<title>El protocolo de texto devuelve cadenas de manera predeterminado</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
if (!$mysqli->query("DROP TABLE IF EXISTS test") ||
!$mysqli->query("CREATE TABLE test(id INT, etiqueta CHAR(1))") ||
!$mysqli->query("INSERT INTO test(id, etiqueta) VALUES (1, 'a')")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
$resultado = $mysqli->query("SELECT id, etiqueta FROM test WHERE id = 1");
$fila = $resultado->fetch_assoc();
printf("id = %s (%s)\n", $fila['id'], gettype($fila['id']));
printf("etiqueta = %s (%s)\n", $fila['etiqueta'], gettype($fila['etiqueta']));
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (string)
etiqueta = a (string)
]]>
</screen>
</example>
</para>
<para>
Es posible convertir valores de columnas de tipo integer y float a números de PHP estableciendo la
opción de conexión <constant>MYSQLI_OPT_INT_AND_FLOAT_NATIVE</constant>,
si se esta´utilizando la biblioteca mysqlnd. Si se establece, la biblioteca mysqlnd
comprobará los tipos de columna de los metadatos del conjunto de resultados y convertirá las columnas númerocas de SQL
a números de PHP, si el rango de valores del tipo de datos de PHP lo permite.
De esta forma, por ejemplo, las columnas INT de SQL son devueltas como enteros.
</para>
<para>
<example>
<title>Tipos de datos nativos con mysqlnd y la opción de conexión</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = mysqli_init();
$mysqli->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, 1);
$mysqli->real_connect("ejemplo.com", "user", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
if (!$mysqli->query("DROP TABLE IF EXISTS test") ||
!$mysqli->query("CREATE TABLE test(id INT, etiqueta CHAR(1))") ||
!$mysqli->query("INSERT INTO test(id, etiqueta) VALUES (1, 'a')")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
$resultado = $mysqli->query("SELECT id, etiqueta FROM test WHERE id = 1");
$fila = $resultado->fetch_assoc();
printf("id = %s (%s)\n", $fila['id'], gettype($fila['id']));
printf("etiqueta = %s (%s)\n", $fila['etiqueta'], gettype($fila['etiqueta']));
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer)
etiqueta = a (string)
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Véase también</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::init</methodname></member>
<member><methodname>mysqli::options</methodname></member>
<member><methodname>mysqli::real_connect</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli::use_result</methodname></member>
<member><methodname>mysqli::store_result</methodname></member>
<member><methodname>mysqli_result::free</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.prepared-statements">
<title>Sentencias Preparadas</title>
<para>
Las bases de datos MySQL soportan sentencias preparadas. Una sentencia preparada
o una sentencia parametrizada se usa para ejecutar la misma sentencia
repetidamente con gran eficiencia.
</para>
<para>
<emphasis role="bold">Flujo de trabajo básico</emphasis>
</para>
<para>
La ejecución de sentencias preparadas consiste en dos etapas:
la preparación y la ejecución. En la etapa de preparación se envía una plantilla de sentencia
al servidor de bases de datos. El servidor realiza una comprobación de sintaxis e inicializa
los recursos internos del servidor para su uso posterior.
</para>
<para>
El servidor de MySQL soporta el uso de parámetros de sustitución posicionales anónimos
con <literal>?</literal>.
</para>
<para>
<example>
<title>Primera etapa: prepación</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
/* Sentencia no preparada */
if (!$mysqli->query("DROP TABLE IF EXISTS test") || !$mysqli->query("CREATE TABLE test(id INT)")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
/* Sentencia preparada, etapa 1: preparación */
if (!($sentencia = $mysqli->prepare("INSERT INTO test(id) VALUES (?)"))) {
echo "Falló la preparación: (" . $mysqli->errno . ") " . $mysqli->error;
}
?>
]]>
</programlisting>
</example>
</para>
<para>
La preparación es seguida de la ejecución. Durante la ejecución el cliente vincula
los valores de los parámetros y los envía al servidor. El servidor crea una
sentencia desde la plantilla de la sentencia y los valores vinculados para
ejecutarla usando los recursos internos previamente creados.
</para>
<para>
<example>
<title>Segunda etapa: vincular y ejecutar</title>
<programlisting role="php">
<![CDATA[
<?php
/* Sentencia preparada, etapa 2: vincular y ejecutar */
$id = 1;
if (!$sentencia->bind_param("i", $id)) {
echo "Falló la vinculación de parámetros: (" . $sentencia->errno . ") " . $sentencia->error;
}
if (!$sentencia->execute()) {
echo "Falló la ejecución: (" . $sentencia->errno . ") " . $sentencia->error;
}
?>
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Ejecución repetida</emphasis>
</para>
<para>
Una sentencia preparada se puede ejecutar repetidamente. En cada ejecución
el valor actual de la variable vinculada se evalúa y se envía al servidor.
La sentencia no se analiza de nuevo. La plantilla de la sentencia no
es transferida otra vez al servidor.
</para>
<para>
<example>
<title>INSERT preparada una vez, ejecutada múltiples veces</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
/* Sentencia no preparada */
if (!$mysqli->query("DROP TABLE IF EXISTS test") || !$mysqli->query("CREATE TABLE test(id INT)")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
/* Sentencia preparada, etapa 1: preparación */
if (!($sentencia = $mysqli->prepare("INSERT INTO test(id) VALUES (?)"))) {
echo "Falló la preparación: (" . $mysqli->errno . ") " . $mysqli->error;
}
/* Sentencia preparada, etapa 2: vinculación y ejecución */
$id = 1;
if (!$sentencia->bind_param("i", $id)) {
echo "Falló la vinculación de parámetros: (" . $sentencia->errno . ") " . $sentencia->error;
}
if (!$sentencia->execute()) {
echo "Falló la ejecución: (" . $sentencia->errno . ") " . $sentencia->error;
}
/* Sentencia preparada: ejecución repetida, sólo datos transferidos desde el cliente al servidor */
for ($id = 2; $id < 5; $id++) {
if (!$sentencia->execute()) {
echo "Falló la ejecución: (" . $sentencia->errno . ") " . $sentencia->error;
}
}
/* se recomienda el cierre explícito */
$sentencia->close();
/* Sentencia no preparada */
$resultado = $mysqli->query("SELECT id FROM test");
var_dump($resultado->fetch_all());
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(4) {
[0]=>
array(1) {
[0]=>
string(1) "1"
}
[1]=>
array(1) {
[0]=>
string(1) "2"
}
[2]=>
array(1) {
[0]=>
string(1) "3"
}
[3]=>
array(1) {
[0]=>
string(1) "4"
}
}
]]>
</screen>
</example>
</para>
<para>
Cada sentencia preparada ocupa recursos del servidor.
Las sentencias deberían cerrarse explícitamente inmediatamente después de su uso.
Si no se realiza explícitamente, la sentencia será cerrada cuando
el gestor de la sentencia sea liberado por PHP.
</para>
<para>
Usar una sentencia preparada no es siempre la manera más
eficiente de ejecutar una sentencia. Una sentencia preparada ejecutada una sola
vez causa más viajes de ida y vuelta desde el cliente al servidor que una sentencia no preparada.
Es por esta razón por lo que <literal>SELECT</literal> no se ejecuta arriba como
una sentencia preparada.
</para>
<para>
También se ha de considerar el uso de la sintaxis SQL multi-INSERT de MySQL para sentencias INSERT.
Por ejemplo, multi-INSERT requiere menos viajes de ida y vuelta entre
el servidor y el cliente que la sentencia preparada mostrada arriba.
</para>
<para>
<example>
<title>Menos viajes de ida y vuelta usando SQL multi-INSERT</title>
<programlisting role="php">
<![CDATA[
<?php
if (!$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3), (4)")) {
echo "Falló multi-INSERT: (" . $mysqli->errno . ") " . $mysqli->error;
}
?>
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Tipos de datos de los valores del conjunto de resultados</emphasis>
</para>
<para>
El Protocolo Cliente Servidor de MySQL define un protocolo de transporte de datos diferente
para sentencias preparadas y no preparadas. Las sentencias preparadas
usan el llamado protocolo binario. El servidor de MySQL envía los datos
del conjunto de resultados "tal cual" en formato binario. Los resultados no son serializados en
cadenas antes del envío. Las bibliotecas cliente no reciben cadenas solamente.
En su lugar, recibirán datos binarios e intentarán convertir los valores a
los tipos de datos de PHP apropiados. Por ejemplo, los resultados de una columna
de SQL <literal>INT</literal> serán proporcionados como variables de tipo integer de PHP.
</para>
<para>
<example>
<title>Tipos de datos nativos</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
if (!$mysqli->query("DROP TABLE IF EXISTS test") ||
!$mysqli->query("CREATE TABLE test(id INT, etiqueta CHAR(1))") ||
!$mysqli->query("INSERT INTO test(id, etiqueta) VALUES (1, 'a')")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
$sentencia = $mysqli->prepare("SELECT id, etiqueta FROM test WHERE id = 1");
$sentencia->execute();
$resultado = $sentencia->get_result();
$fila = $resultado->fetch_assoc();
printf("id = %s (%s)\n", $fila['id'], gettype($fila['id']));
printf("etiqueta = %s (%s)\n", $fila['etiqueta'], gettype($fila['etiqueta']));
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer)
etiqueta = a (string)
]]>
</screen>
</example>
</para>
<para>
Este comportamiento difiere de las sentencias no preparadas. Por defecto,
las sentencias no preparadas devolverán todos los resultados como cadenas.
Este comportamiento predeterminado se puede cambiar usand una opción de conexión.
Si se utiliza la opción de conexión no existirán diferencias.
</para>
<para>
<emphasis role="bold">Obtener resultados usando variables vinculadas</emphasis>
</para>
<para>
Los resultados de las sentencias preparadas pueden ser recuperados mediante
varibles de salida vinculadas, o por la petición de un objeto <classname>mysqli_result</classname>.
</para>
<para>
Las variables de salida deben ser vinculadas después de la ejecución de la sentencia.
Una variable debe estar vinculada para cada columna del conjunto de resultados de las sentencias.
</para>
<para>
<example>
<title>Vinculación de variables de salida</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
if (!$mysqli->query("DROP TABLE IF EXISTS test") ||
!$mysqli->query("CREATE TABLE test(id INT, etiqueta CHAR(1))") ||
!$mysqli->query("INSERT INTO test(id, etiqueta) VALUES (1, 'a')")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!($sentencia = $mysqli->prepare("SELECT id, etiqueta FROM test"))) {
echo "Falló la preparación: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$sentencia->execute()) {
echo "Falló la ejecución: (" . $mysqli->errno . ") " . $mysqli->error;
}
$id_salida = NULL;
$etiqueta_salida = NULL;
if (!$sentencia->bind_result($id_salida, $etiqueta_salida)) {
echo "Falló la vinculación de los parámetros de salida: (" . $sentencia->errno . ") " . $sentencia->error;
}
while ($sentencia->fetch()) {
printf("id = %s (%s), etiqueta = %s (%s)\n", $id_salida, gettype($id_salida), $etiqueta_salida, gettype($etiqueta_salida));
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer), etiqueta = a (string)
]]>
</screen>
</example>
</para>
<para>
Las sentencias preparadas devuelven de manera predeterminada conjuntos de resultados no almacenados en buffer.
Los resultados de la sentencia no son obtenidas y transferidas implícitamente
desde el servidor al cliente para el almacenamiento en buffer de lado del cliente. El conjunto de resultados
toma recursos del servidor hasta que todos los resultados hayan sido obtenidos por el cliente.
Por lo que se recomienda consumir resultados cuando sea oportuno. Si un cliente falla al obtener todos
los resultados o el cliente cierra la consulta antes de haber obtenido todos los datos,
los datos han de ser obtenidos implícitamente por <literal>mysqli</literal>.
</para>
<para>
También es posible almacenar en buffer los resutados de una sentencia preparada
usando <function>mysqli_stmt_store_result</function>.
</para>
<para>
<emphasis role="bold">Obtener los resultados usando la interfaz mysqli_result</emphasis>
</para>
<para>
En lugar de usar resultados vinculados, los resultados también se pueden recuperar a través de la
interfaz mysqli_result. <function>mysqli_stmt_get_result</function>
devuelve un conjunto de resultados almacenado en buffer.
</para>
<para>
<example>
<title>Usar mysqli_result para obtener los resultados</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
if (!$mysqli->query("DROP TABLE IF EXISTS test") ||
!$mysqli->query("CREATE TABLE test(id INT, etiqueta CHAR(1))") ||
!$mysqli->query("INSERT INTO test(id, etiqueta) VALUES (1, 'a')")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!($sentencia = $mysqli->prepare("SELECT id, etiqueta FROM test ORDER BY id ASC"))) {
echo "Falló la preparación: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$sentencia->execute()) {
echo "Falló la ejecución: (" . $sentencia->errno . ") " . $sentencia->error;
}
if (!($resultado = $sentencia->get_result())) {
echo "Falló la obtención del conjunto de resultados: (" . $sentencia->errno . ") " . $sentencia->error;
}
var_dump($resultado->fetch_all());
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
[0]=>
array(2) {
[0]=>
int(1)
[1]=>
string(1) "a"
}
}
]]>
</screen>
</example>
</para>
<para>
Usar la <classname>interfaz mysqli_result</classname> ofrece el beneficio adicional de
la navegación flexible del conjunto de resultados en el lado del cliente.
</para>
<para>
<example>
<title>Conjunto de resultados almacenado en buffer para la lectura flexible</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
if (!$mysqli->query("DROP TABLE IF EXISTS test") ||
!$mysqli->query("CREATE TABLE test(id INT, etiqueta CHAR(1))") ||
!$mysqli->query("INSERT INTO test(id, etiqueta) VALUES (1, 'a'), (2, 'b'), (3, 'c')")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!($sentencia = $mysqli->prepare("SELECT id, etiqueta FROM test"))) {
echo "Falló la preparación: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$sentencia->execute()) {
echo "Falló la ejecución: (" . $sentencia->errno . ") " . $sentencia->error;
}
if (!($resultado = $sentencia->get_result())) {
echo "Falló la obtención del conjunto de resultados: (" . $sentencia->errno . ") " . $sentencia->error;
}
for ($num_fila = ($resultado->num_rows - 1); $num_fila >= 0; $num_fila--) {
$resultado->data_seek($num_fila);
var_dump($resultado->fetch_assoc());
}
$resultado->close();
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(2) {
["id"]=>
int(3)
["etiqueta"]=>
string(1) "c"
}
array(2) {
["id"]=>
int(2)
["etiqueta"]=>
string(1) "b"
}
array(2) {
["id"]=>
int(1)
["etiqueta"]=>
string(1) "a"
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Escape de valores e inyección SQL</emphasis>
</para>
<para>
Las variables vinculadas son enviadas desde la consulta al servidor por separado, por lo que
no se puede interferir. El servidor usa estos valores directamente en el momento
de la ejecución, después de haber analizado la plantilla de la sentencia. Los parámetros vinculados no
necesitan ser escapados porque nunca son sustituidos directamente dentro del string de
consulta. Se puede proporcionar una sugerencia al servidor para el tipo de variable
vinculada, para crear una conversión apropiada.
Véase la función <function>mysqli_stmt_bind_param</function> para más
información.
</para>
<para>
A veces, tal separación es considerada como la única característica de seguridad para
evitar inyecciones SQL, pero se puede alcanzar el mismo grado de seguridad con
sentencias no preparadas, si todos los valores están formateados correctamente. Debería
observarse que el formateo correcto no es lo mismo que usar escapes, y que involucra
más lógica que simplemente usar escapes. Por lo tanto, las sentencias preparadas son sencillamente una
estrategia más conveniente y menos propensa a errores para este elemetno de seguridad de bases de datos.
</para>
<para>
<emphasis role="bold">Emulación de sentencias preparadas en el lado del cliente</emphasis>
</para>
<para>
La API no inclye la emulación para sentencias preparadas en el lado del cliente.
</para>
<para>
<emphasis role="bold">Comparación entre sentencias preparadas y no preparadas</emphasis>
</para>
<para>
La tabla de abajo compara las sentencias preparadas y no preparadas del lado del servidor.
</para>
<table>
<title>Comparación entre sentencias preparadas y no preparadas</title>
<tgroup cols="3">
<thead>
<row>
<entry></entry>
<entry>Sentencia preparada</entry>
<entry>Sentencia no preparada</entry>
</row>
</thead>
<tbody>
<row>
<entry>Viajes de ida y vuelta desde el cliente al servidor, SELECT, ejecución única</entry>
<entry>2</entry>
<entry>1</entry>
</row>
<row>
<entry>Cadenas de sentencias tranferidas desde el cliente al servidor</entry>
<entry>1</entry>
<entry>1</entry>
</row>
<row>
<entry>Viajes de ida y vuelta desde el cliente al servidor, SELECT, ejecución repetida (n)</entry>
<entry>1 + n</entry>
<entry>n</entry>
</row>
<row>
<entry>Cadenas de sentencias tranferidas desde el cliente al servidor</entry>
<entry>1 plantilla, n veces parametro vinculado, si existe</entry>
<entry>n veces junto con el parámetro, si existe</entry>
</row>
<row>
<entry>API de vinculación de parámetros de entrada</entry>
<entry>Sí, escape de entradas automático</entry>
<entry>No, escape de entradas manual</entry>
</row>
<row>
<entry>API de vinculación de variables de salida</entry>
<entry></entry>
<entry>No</entry>
</row>
<row>
<entry>Soporte para el uso de la API mysqli_result</entry>
<entry>Sí, use <function>mysqli_stmt_get_result</function></entry>
<entry></entry>
</row>
<row>
<entry>Conjuntos de resultados almacenados en buffer</entry>
<entry>
Sí, use <function>mysqli_stmt_get_result</function> o
la vinculación con <function>mysqli_stmt_store_result</function>
</entry>
<entry>Sí, lo predeterminado de <function>mysqli_query</function></entry>
</row>
<row>
<entry>Conjuntos de resultados no almacenados en buffer</entry>
<entry>Sí, use la API de vinculación de salida</entry>
<entry>
Sí, use <function>mysqli_real_query</function> con
<function>mysqli_use_result</function>
</entry>
</row>
<row>
<entry>"Sabor" de la transferencia de datos del protocolo Cliente Servidor de MySQL</entry>
<entry>Protocolo binario</entry>
<entry>Protocolo de texto</entry>
</row>
<row>
<entry>Tipos de datos SQL de los valores del conjunto de resultados</entry>
<entry>Preservados al obtenerlos</entry>
<entry>Convertidos a cadena o preservados al obetenerlos</entry>
</row>
<row>
<entry>Soporta todas las sentencia SQL</entry>
<entry>Versiones reciente de MySQL soportan muchas pero no todas</entry>
<entry></entry>
</row>
</tbody>
</tgroup>
</table>
<para>
<emphasis role="bold">Véase también</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::prepare</methodname></member>
<member><methodname>mysqli_stmt::prepare</methodname></member>
<member><methodname>mysqli_stmt::execute</methodname></member>
<member><methodname>mysqli_stmt::bind_param</methodname></member>
<member><methodname>mysqli_stmt::bind_result</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.stored-procedures">
<title>Procedimientos almacenados</title>
<para>
Las bases de datos de MySQL soportan procedimientos almacenados. Un procedimiento almacenado es una
subrutina almacenada en el catálogo de la base de datos. Las aplicaciones pueden llamar y
ejecutar el procedimiento almacenado. La sentencia de SQL <literal>CALL</literal>
se usa para ejecutar un procedimiento almacenado.
</para>
<para>
<emphasis role="bold">Parámetros</emphasis>
</para>
<para>
Los procedimientos almacenados pueden tener parámetros <literal>IN</literal>,
<literal>INOUT</literal> y <literal>OUT</literal>,
dependiendo de la versión de MySQL. La interfaz mysqli no tiene una noción
especial de los diferentes tipos de parámetros.
</para>
<para>
<emphasis role="bold">Parámetro IN</emphasis>
</para>
<para>
Los parámetros de entrada son proporcionados con la sentencia <literal>CALL</literal>.
Asegúrese de que los valores están escapados correctamente.
</para>
<para>
<example>
<title>Llamar a un procedimiento almacenado</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
if (!$mysqli->query("DROP TABLE IF EXISTS test") || !$mysqli->query("CREATE TABLE test(id INT)")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$mysqli->query("DROP PROCEDURE IF EXISTS p") ||
!$mysqli->query("CREATE PROCEDURE p(IN id_val INT) BEGIN INSERT INTO test(id) VALUES(id_val); END;")) {
echo "Falló la creación del procedimiento almacenado: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$mysqli->query("CALL p(1)")) {
echo "Falló CALL: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!($resultado = $mysqli->query("SELECT id FROM test"))) {
echo "Falló SELECT: (" . $mysqli->errno . ") " . $mysqli->error;
}
var_dump($resultado->fetch_assoc());
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
["id"]=>
string(1) "1"
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Parámetro INOUT/OUT</emphasis>
</para>
<para>
A los valores de los parámetros <literal>INOUT</literal>/<literal>OUT</literal>
se acceden usando variables de sesión.
</para>
<para>
<example>
<title>Usar variables de sesión</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
if (!$mysqli->query("DROP PROCEDURE IF EXISTS p") ||
!$mysqli->query('CREATE PROCEDURE p(OUT msg VARCHAR(50)) BEGIN SELECT "¡Hola!" INTO msg; END;')) {
echo "Falló la creación del procedimiento almacenado: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$mysqli->query("SET @msg = ''") || !$mysqli->query("CALL p(@msg)")) {
echo "Falló CALL: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!($resultado = $mysqli->query("SELECT @msg as _p_out"))) {
echo "Falló la obtención: (" . $mysqli->errno . ") " . $mysqli->error;
}
$fila = $resultado->fetch_assoc();
echo $fila['_p_out'];
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
¡Hola!
]]>
</screen>
</example>
</para>
<para>
Los desarrolladores de aplicaciones y framework pueden proporcionar una API más
conveniente usando una mezcla de variables de sesiones e inspección del catálogo de la base de datos.
Sin embargo, observe el posible impacto de rendimiento de una solución
personalizada basada en la inspección del catálogo.
</para>
<para>
<emphasis role="bold">Manejar conjuntos de resultados</emphasis>
</para>
<para>
Los procedimientos almacenados pueden devolver conjuntos de resultados. Los conjuntos de resultados devueltos desde
un procedimiento almacenado no se pueden obtener correctgamente usando <function>mysqli_query</function>.
La función <function>mysqli_query</function> combina la ejecución de la sentencia
y la obtención del primer conjunto de resultados dentro de un conjunto de resultados almacenado en buffer, si existe.
Sin embargo, existen unos conjuntos de resultados del procedimiento almacenado ocultos
para el usuario que hacen que <function>mysqli_query</function> falle
al devolver los conjuntos de resultados esperados por el usuario.
</para>
<para>
Los conjuntos de resultados devueltos desde un procedimiento almacenado son obtenidos usando
<function>mysqli_real_query</function> o <function>mysqli_multi_query</function>.
Ambas funciones permiten la obtención de cualquier número de conjuntos de resultados devueltos
por una sentencia, como <literal>CALL</literal>. El fallo de la obtención de todos
los conjuntos de resultados devueltos por un procedimiento almacenado causa un error.
</para>
<para>
<example>
<title>Obtener los resultados de procedimientos almacenados</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $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), (2), (3)")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$mysqli->query("DROP PROCEDURE IF EXISTS p") ||
!$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;')) {
echo "Falló la creación del procedimiento almacenado: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$mysqli->multi_query("CALL p()")) {
echo "Falló CALL: (" . $mysqli->errno . ") " . $mysqli->error;
}
do {
if ($resultado = $mysqli->store_result()) {
printf("---\n");
var_dump($resultado->fetch_all());
$resultado->free();
} else {
if ($mysqli->errno) {
echo "Store failed: (" . $mysqli->errno . ") " . $mysqli->error;
}
}
} while ($mysqli->more_results() && $mysqli->next_result());
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
---
array(3) {
[0]=>
array(1) {
[0]=>
string(1) "1"
}
[1]=>
array(1) {
[0]=>
string(1) "2"
}
[2]=>
array(1) {
[0]=>
string(1) "3"
}
}
---
array(3) {
[0]=>
array(1) {
[0]=>
string(1) "2"
}
[1]=>
array(1) {
[0]=>
string(1) "3"
}
[2]=>
array(1) {
[0]=>
string(1) "4"
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Uso de sentencias preparadas</emphasis>
</para>
<para>
No es necesario un trato especial al usar la interfaz de sentencias
preparadas para obtener los resultados del mismo procedimiento almacenado de arriba.
Las interfaces de sentencias preparadas y no preparadas son similares.
Obserque que no todas las versioines del servidor de MYSQL pueden soportar
la preparación de la sentencia SQL <literal>CALL</literal>.
</para>
<para>
<example>
<title>Procedimientos Almacenados y Sentencias Preparadas</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $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), (2), (3)")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$mysqli->query("DROP PROCEDURE IF EXISTS p") ||
!$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;')) {
echo "Falló la creación del procedimiento almacenado: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!($sentencia = $mysqli->prepare("CALL p()"))) {
echo "Falló la preparación: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$sentencia->execute()) {
echo "Falló la ejecución: (" . $sentencia->errno . ") " . $sentencia->error;
}
do {
if ($resultado = $sentencia->get_result()) {
printf("---\n");
var_dump(mysqli_fetch_all($resultado));
mysqli_free_result($resultado);
} else {
if ($sentencia->errno) {
echo "Store failed: (" . $sentencia->errno . ") " . $sentencia->error;
}
}
} while ($sentencia->more_results() && $sentencia->next_result());
?>
]]>
</programlisting>
</example>
</para>
<para>
Por supuesto, tamibién está soportado el uso de la API de vinculación para la obtención.
</para>
<para>
<example>
<title>Procedimientos Almacenados y Sentencias Preparadas usando la API de vinculación</title>
<programlisting role="php">
<![CDATA[
<?php
if (!($sentencia = $mysqli->prepare("CALL p()"))) {
echo "Falló la preparación: (" . $mysqli->errno . ") " . $mysqli->error;
}
if (!$sentencia->execute()) {
echo "Falló la ejecución: (" . $sentencia->errno . ") " . $sentencia->error;
}
do {
$id_out = NULL;
if (!$sentencia->bind_result($id_out)) {
echo "Falló la vinculiación: (" . $sentencia->errno . ") " . $sentencia->error;
}
while ($sentencia->fetch()) {
echo "id = $id_out\n";
}
} while ($sentencia->more_results() && $sentencia->next_result());
?>
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Véase también</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli_result::next-result</methodname></member>
<member><methodname>mysqli_result::more-results</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.multiple-statement">
<title>Sentencias Múltiples</title>
<para>
MySQL permite opcionalmente tener múltiples sentencias en una cadena de sentencias.
El envío de múltiples sentencias de una sola vez reduce los viajes de ida y vuelta desde
el cliente al servidor, pero requiere un manejo especial.
</para>
<para>
Las sentencias múltiples o multiconsultas deben ser ejecutadas
con <function>mysqli_multi_query</function>. Las sentencias individuales
de la cadena de sentencias están serparadas por un punto y coma.
Entonces, todos los conjuntos de resultados devueltos por las sentencias ejecutadas deben ser obtenidos.
</para>
<para>
El servidor MySQL permite tener sentencias que devuelven conjuntos de resultados y
sentencias que no devuelve conjuntos de resultados en una sentencia múltiple.
</para>
<para>
<example>
<title>Sentencias múltiples</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
if (!$mysqli->query("DROP TABLE IF EXISTS test") || !$mysqli->query("CREATE TABLE test(id INT)")) {
echo "Falló la creación de la tabla: (" . $mysqli->errno . ") " . $mysqli->error;
}
$sql = "SELECT COUNT(*) AS _num FROM test; ";
$sql.= "INSERT INTO test(id) VALUES (1); ";
$sql.= "SELECT COUNT(*) AS _num FROM test; ";
if (!$mysqli->multi_query($sql)) {
echo "Falló la multiconsulta: (" . $mysqli->errno . ") " . $mysqli->error;
}
do {
if ($resultado = $mysqli->store_result()) {
var_dump($resultado->fetch_all(MYSQLI_ASSOC));
$resultado->free();
}
} while ($mysqli->more_results() && $mysqli->next_result());
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
[0]=>
array(1) {
["_num"]=>
string(1) "0"
}
}
array(1) {
[0]=>
array(1) {
["_num"]=>
string(1) "1"
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Consideraciones de seguridad</emphasis>
</para>
<para>
Las funciones de la API <function>mysqli_query</function> y
<function>mysqli_real_query</function> no establecen una bandera de conexión necesaria
para activar las multiconsultas en el servidor. Se usa una llamada extra a la API para
las sentencias múltiples para reducir la verosimilitud de los ataques de inyecciones SQL
accidentales. Un atacante puede intentar añadir sentencias como
<literal>; DROP DATABASE mysql</literal> o <literal>; SELECT SLEEP(999)</literal>.
Si el atacante tiene éxito al añadir SQL a la cadena de sentencias pero
no se usa <literal>mysqli_multi_query</literal>, el servidor no
ejecutará la segunda sentencia SQL inyectada y maliciosa.
</para>
<para>
<example>
<title>Inyección SQL</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
$resultado = $mysqli->query("SELECT 1; DROP TABLE mysql.user");
if (!$resultado) {
echo "Error al ejecutar la consulta: (" . $mysqli->errno . ") " . $mysqli->error;
}
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Error al ejecutar la consulta: (1064) You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax
to use near 'DROP TABLE mysql.user' at line 1
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Sentencias preparadas</emphasis>
</para>
<para>
El uso de sentencias múltiples con sentencias preparadas no está soportado.
</para>
<para>
<emphasis role="bold">See also</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli_result::next-result</methodname></member>
<member><methodname>mysqli_result::more-results</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.transactions">
<title>Soporte de la API para transacciones</title>
<para>
El servidor MySQL soporta transacciones dependiendo de del motor de almacenamiento usado.
Desde MySQL 5.5, el motor de almacenamiento predeterminado es InnoDB.
InnoDB tiene soporte completo para transacciones ACID.
</para>
<para>
Las transacciones se pueden controlar usando SQL o llamadas a la API.
Se recomienda usar llamadas a la API para habilitar y deshabilitar el
modo de autoconsignación (auto commit) y para consignar y reiniciar transacciones.
</para>
<para>
<example>
<title>Establecer el modo de autoconsignación con SQL y a través de la API</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
/* Recomendado: usar la API para cotrolar las configuraciones transaccionales */
$mysqli->autocommit(false);
/* No serán monitorizadas y reconocidas por la aplicación y el complemento de balance de carga */
if (!$mysqli->query('SET AUTOCOMMIT = 0')) {
echo "Falló la consulta: (" . $mysqli->errno . ") " . $mysqli->error;
}
?>
]]>
</programlisting>
</example>
</para>
<para>
Los paquetes de características opcionales, como los comlementos de replicación y el de balance de carga,
pueden fácilmente monitorizar llamadas a la API. El complemento de replicación transacciones
conscientes del balance de carga, si las transacciones están controladas con llamadas a la API.
Las transacciones conscientes del balance de carga no están disponibles si las sentencias SQL se
usan para establecer el modo de autoconsignación, consignación y reinicio de una transacción.
</para>
<para>
<example>
<title>Consignación y reinicio</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
$mysqli->autocommit(false);
$mysqli->query("INSERT INTO test(id) VALUES (1)");
$mysqli->rollback();
$mysqli->query("INSERT INTO test(id) VALUES (2)");
$mysqli->commit();
?>
]]>
</programlisting>
</example>
</para>
<para>
Observe que el servidor MySQL no puede reiniciar todas las sentencias.
Algunoas sentencias causan una consignación implícia.
</para>
<para>
<emphasis role="bold">Véase también</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::autocommit</methodname></member>
<member><methodname>mysqli_result::commit</methodname></member>
<member><methodname>mysqli_result::rollback</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.metadata">
<title>Metadatos</title>
<para>
Un conjunto de resultados de MySQL contiene metadatos. Los metadatos describen las columnas
encontradas en el conjunto de resultados. Todos los metadatos enviados por MySQL son accesibles
a travbés de la interfaz de <literal>mysqli</literal>.
La extensión realiza cambios insignificantes o no realiza ninguno a la
información que recibe.
La diferencias entre versiones del servidor MySQL no están alineadas.
</para>
<para>
A los metadatos se puede acceder a través de la interfaz <classname>mysqli_result</classname>.
</para>
<para>
<example>
<title>Acceder a los metadatos de un conjunto de resultados</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("ejemplo.com", "usuario", "contraseña", "basedatos");
if ($mysqli->connect_errno) {
echo "Falló la conexión a MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
$resultado = $mysqli->query("SELECT 1 AS _one, 'Hello' AS _two FROM DUAL");
var_dump($resultado->fetch_fields());
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(2) {
[0]=>
object(stdClass)#3 (13) {
["name"]=>
string(4) "_one"
["orgname"]=>
string(0) ""
["table"]=>
string(0) ""
["orgtable"]=>
string(0) ""
["def"]=>
string(0) ""
["db"]=>
string(0) ""
["catalog"]=>
string(3) "def"
["max_length"]=>
int(1)
["length"]=>
int(1)
["charsetnr"]=>
int(63)
["flags"]=>
int(32897)
["type"]=>
int(8)
["decimals"]=>
int(0)
}
[1]=>
object(stdClass)#4 (13) {
["name"]=>
string(4) "_two"
["orgname"]=>
string(0) ""
["table"]=>
string(0) ""
["orgtable"]=>
string(0) ""
["def"]=>
string(0) ""
["db"]=>
string(0) ""
["catalog"]=>
string(3) "def"
["max_length"]=>
int(5)
["length"]=>
int(5)
["charsetnr"]=>
int(8)
["flags"]=>
int(1)
["type"]=>
int(253)
["decimals"]=>
int(31)
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Sentencias preparadas</emphasis>
</para>
<para>
A los metadatos de un conjunto de resultados creado usando sentencias preparadas se accede
de la misma manera. Un gestor de <classname>mysqli_result</classname> apropiado es
devuelto por <function>mysqli_stmt_result_metadata</function>.
</para>
<para>
<example>
<title>Metadatos de sentencias preparadas</title>
<programlisting role="php">
<![CDATA[
<?php
$sentencia = $mysqli->prepare("SELECT 1 AS _one, 'Hello' AS _two FROM DUAL");
$sentencia->execute();
$resultado = $sentencia->result_metadata();
var_dump($resultado->fetch_fields());
?>
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Véase también</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli_result::fetch_fields</methodname></member>
</simplelist>
</para>
</section>
</chapter>