El autor seleccionó Open Sourcing Mental Illness para recibir una donación como parte del programa Write for DOnations.
Una transacción de MySQL es un conjunto de comandos SQL que guardan una relación lógica y se ejecutan en la base de datos como una sola unidad. Las transacciones se utilizan para garantizar el cumplimiento de ACID (atomicidad, consistencia, aislamiento y durabilidad, por su sigla en inglés) en las aplicaciones. Se trata de un conjunto de normas que determinan la confiabilidad de las operaciones de procesamiento de las bases de datos.
La atomicidad garantiza que las transacciones relacionadas sean correctas o no surtan ningún efecto si se produce un error. La consistencia garantiza la validez de los datos enviados a la base de datos según la lógica de negocios definida. El aislamiento es la correcta ejecución de transacciones simultáneas que garantiza que los efectos de los distintos clientes que se conectan a una base de datos no se afecten entre sí. La durabilidad garantiza que las transacciones con relación lógica se conserven en la base de datos de forma permanente.
Las instrucciones SQL que se emiten a través de una transacción deben ser correctas fracasar por completo. Si alguna consulta falla, MySQL revierte los cambios y no se confirman en la base de datos.
Un buen ejemplo para comprender cómo funcionan las transacciones de MySQL es un sitio web de comercio electrónico. Cuando un cliente realiza un pedido, la aplicación inserta registros en varias tablas, por ejemplo, orders
y orders_products
, dependiendo de la lógica de negocio. Los registros de varias tablas relacionados con un único pedido se deben enviar de forma atómica a la base de datos como una sola unidad lógica.
Otro caso de uso es una aplicación bancaria. Cuando un cliente transfiere dinero, se envían algunas transacciones a la base de datos. Se debita dinero de la cuenta del remitente y se acredita en la del receptor. Las dos transacciones se deben confirmar de forma simultánea. Si una de ellas falla, la base de datos volverá a su estado original y no se guardarán cambios en el disco.
En el caso de este tutorial, usará la extensión PHP PDO, que ofrece una interfaz para trabajar con bases de datos en PHP, para realizar transacciones de MySQL en un servidor de Ubuntu 18.04.
Para comenzar, necesitará lo siguiente:
Primero, creará una base de datos de ejemplo y agregará algunas tablas para comenzar a trabajar con transacciones de MySQL. Primero, inicie sesión en su servidor MySQL como root:
- sudo mysql -u root -p
Cuando se le solicite, introduzca su contraseña root de MySQL y presione INTRO
para continuar. Luego, cree una base de datos. En este tutorial, la denominaremos sample_store
:
- CREATE DATABASE sample_store;
Verá lo siguiente:
OutputQuery OK, 1 row affected (0.00 sec)
Cree un usuario llamado sample_user
para su base de datos. Recuerde sustituir PASSWORD
por un valor seguro:
- CREATE USER 'sample_user'@'localhost' IDENTIFIED BY 'PASSWORD';
Conceda privilegios completos a su usuario para la base de datos sample_store
:
- GRANT ALL PRIVILEGES ON sample_store.* TO 'sample_user'@'localhost';
Por último, vuelva a cargar los privilegios de MySQL:
- FLUSH PRIVILEGES;
Una vez que cree su usuario, verá el siguiente resultado:
OutputQuery OK, 0 rows affected (0.01 sec)
. . .
Ahora, con la base de datos y el usuario establecidos, puede crear varias tablas para ver cómo funcionan las transacciones de MySQL.
Cierre sesión en el servidor de MySQL:
- QUIT;
Una vez que el sistema cierre su sesión, verá el siguiente resultado:
OutputBye.
Luego, inicie sesión con las credenciales de sample_user
que acaba de crear:
- sudo mysql -u sample_user -p
Ingrese la contraseña de sample_user
y presione INTRO
para continuar.
Pase a sample_store
para que sea la base de datos seleccionada:
- USE sample_store;
Una vez que la seleccione, verá el siguiente resultado:
OutputDatabase Changed.
A continuación, cree una tabla products
:
- CREATE TABLE products (product_id BIGINT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(50), price DOUBLE) ENGINE = InnoDB;
Este comando crea una tabla products
con un campo denominado product_id
. Se utiliza el tipo de datos BIGINT
, que puede admitir un valor elevado de hasta 2^63-1. Se utiliza este mismo campo como PRIMARY KEY
para identificar productos de forma única. La palabra clave AUTO_INCREMENT
indica a MySQL que genere el siguiente valor numérico cuando se insertan nuevos productos.
El campo product_name
es de tipo VARCHAR
y puede contener hasta 50
letras o números. En price
del producto, se utiliza el tipo de datos DOUBLE
para admitir formatos de punto flotante en los precios con números decimales.
Por último, se utiliza InnoDB
como ENGINE
porque admite fácilmente las transacciones de MySQL a diferencia de otros motores de almacenamiento, como MyISAM
.
Cuando haya creado su tabla products
, verá el siguiente resultado:
OutputQuery OK, 0 rows affected (0.02 sec)
A continuación, añada algunos elementos a la tabla products
al ejecutar los siguientes comandos:
- INSERT INTO products(product_name, price) VALUES ('WINTER COAT','25.50');
- INSERT INTO products(product_name, price) VALUES ('EMBROIDERED SHIRT','13.90');
- INSERT INTO products(product_name, price) VALUES ('FASHION SHOES','45.30');
- INSERT INTO products(product_name, price) VALUES ('PROXIMA TROUSER','39.95');
Verá un resultado similar al siguiente después de cada operación INSERT
:
OutputQuery OK, 1 row affected (0.02 sec)
. . .
Luego, compruebe que se hayan agregado los datos a la tabla products:
- SELECT * FROM products;
Verá una lista con los cuatro productos que insertó:
Output+------------+-------------------+-------+
| product_id | product_name | price |
+------------+-------------------+-------+
| 1 | WINTER COAT | 25.5 |
| 2 | EMBROIDERED SHIRT | 13.9 |
| 3 | FASHION SHOES | 45.3 |
| 4 | PROXIMA TROUSER | 39.95 |
+------------+-------------------+-------+
4 rows in set (0.01 sec)
A continuación, creará una tabla customers
para almacenar información básica sobre los clientes:
- CREATE TABLE customers (customer_id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_name VARCHAR(50) ) ENGINE = InnoDB;
Al igual que en la tabla products
, se utiliza el tipo de datos BIGINT
para customer_id
para garantizar que la tabla pueda admitir muchos clientes, hasta 2^63-1 registros. La palabra clave AUTO_INCREMENT
aumenta el valor de las columnas cuando se inserta un nuevo cliente.
Como la columna customer_name
acepta valores alfanuméricos, se utiliza el tipo de datos VARCHAR
, que tiene un límite de 50
caracteres. Nuevamente, se utiliza InnoDB
como ENGINE
de almacenamiento para admitir las transacciones.
Después de ejecutar el comando anterior para crear la tabla customers
, verá el siguiente resultado:
OutputQuery OK, 0 rows affected (0.02 sec)
Agregará tres clientes de ejemplo a la tabla. Ejecute el siguiente comando:
- INSERT INTO customers(customer_name) VALUES ('JOHN DOE');
- INSERT INTO customers(customer_name) VALUES ('ROE MARY');
- INSERT INTO customers(customer_name) VALUES ('DOE JANE');
Una vez que haya agregado los clientes, verá un resultado similar al siguiente:
OutputQuery OK, 1 row affected (0.02 sec)
. . .
Luego, verifique los datos de la tabla customers
:
- SELECT * FROM customers;
Verá una lista con los tres clientes:
Output+-------------+---------------+
| customer_id | customer_name |
+-------------+---------------+
| 1 | JOHN DOE |
| 2 | ROE MARY |
| 3 | DOE JANE |
+-------------+---------------+
3 rows in set (0.00 sec)
A continuación, creará una tabla orders
para registrar los pedidos de los distintos clientes. Para crear la tabla orders
, ejecute el siguiente comando:
- CREATE TABLE orders (order_id BIGINT AUTO_INCREMENT PRIMARY KEY, order_date DATETIME, customer_id BIGINT, order_total DOUBLE) ENGINE = InnoDB;
Se utiliza la columna order_id
como PRIMARY KEY
. El tipo de datos BIGINT
le permite ingresar hasta 2^63-1 pedidos que se incrementan de forma automática cada vez que se introduce uno nuevo. El campo order_date
contiene la fecha y la hora reales en que se realiza un pedido y, por lo tanto, se utiliza el tipo de datos DATETIME
. El campo customer_id
se relaciona con la tabla customers
que creó anteriormente.
Verá lo siguiente:
OutputQuery OK, 0 rows affected (0.02 sec)
Como el pedido de un cliente puede contener varios elementos, debe crear una tabla orders_products
para almacenar esta información.
Para crear la tabla orders_products
, ejecute el siguiente comando:
- CREATE TABLE orders_products (ref_id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT, product_id BIGINT, price DOUBLE, quantity BIGINT) ENGINE = InnoDB;
Se utiliza ref_id
como PRIMARY KEY
, que se incrementará automáticamente con cada registro que se ingrese. Los campos order_id
y product_id
se relacionan con las tablas orders
y products
respectivamente. La columna price
es de tipo DOUBLE
para poder admitir valores flotantes.
El motor de almacenamiento InnoDB
debe coincidir con las otras tablas creadas anteriormente, dado que el pedido de un cliente afectará varias tablas que utilizan transacciones en simultáneo.
Su resultado confirmará la creación de la tabla:
OutputQuery OK, 0 rows affected (0.02 sec)
Por el momento, no agregará datos a las tablas orders
y orders_products
, pero lo hará más adelante utilizando una secuencia de comandos PHP que implementa transacciones de MySQL.
Cierre sesión en el servidor de MySQL:
- QUIT;
Ahora, su esquema de base de datos está completo y lo completó con algunos registros. Ahora, creará una clase PHP para gestionar las conexiones de la base de datos y las transacciones de MySQL.
En este paso, creará una clase PHP que usará PDO (objetos de datos de PHP) para gestionar transacciones de MySQL. La clase se conectará con su base de datos de MySQL e insertará datos de forma atómica en la base de datos.
Guarde el archivo de clase en el directorio root de su servidor web de Apache. Para hacerlo, cree un archivo DBTransaction.php
con su editor de texto:
- sudo nano /var/www/html/DBTransaction.php
Luego, añada el siguiente código al archivo. Reemplace PASSWORD
por el valor que creó en el paso 1:
<?php
class DBTransaction
{
protected $pdo;
public $last_insert_id;
public function __construct()
{
define('DB_NAME', 'sample_store');
define('DB_USER', 'sample_user');
define('DB_PASSWORD', 'PASSWORD');
define('DB_HOST', 'localhost');
$this->pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASSWORD);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
Al comienzo de la clase DBTransaction
, el PDO utilizará las constantes (DB_HOST
, DB_NAME
, DB_USER
, y DB_PASSWORD
) para inicializar la base de datos que creó en el paso 1 y conectarse a ella.
Nota: Debido a que estamos demostrando transacciones de MySQL en pequeña escala en este tutorial, declaramos las variables de la base de datos en la clase DBTransaction
. En un proyecto de producción de gran envergadura, normalmente, crearía un archivo de configuración separado y cargaría las constantes de la base de datos de ese archivo usando la instrucción de PHP require_once
.
A continuación, establezca dos atributos para la clase PDO:
ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION
: este atributo le indica a PDO que inicie una excepción si se encuentra un error. Los errores se pueden registrar para su depuración.ATTR_EMULATE_PREPARES, false
: esta opción desactiva la emulación de las instrucciones preparadas y permite que el motor de la base de datos de MySQL las prepare por su cuenta.Ahora, añada el siguiente código a su archivo para crear los métodos de su clase:
. . .
public function startTransaction()
{
$this->pdo->beginTransaction();
}
public function insertTransaction($sql, $data)
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($data);
$this->last_insert_id = $this->pdo->lastInsertId();
}
public function submitTransaction()
{
try {
$this->pdo->commit();
} catch(PDOException $e) {
$this->pdo->rollBack();
return false;
}
return true;
}
}
Guarde y cierre el archivo pulsando CTRL
+ X
, Y
e INTRO
.
Para trabajar con las transacciones de MySQL, se crean tres métodos principales en la clase DBTransaction
: startTransaction
, insertTransaction
y submitTransaction
.
startTransaction
: este método le indica a PDO que inicie una transacción y desactive la confirmación automática hasta que se emita un comando de confirmación.
insertTransaction
: este método toma dos argumentos. La variable $sql
contiene la instrucción SQL que se ejecutará y la variable $data
es una matriz de los datos que se vincularán con la instrucción SQL, dado que se utilizarán instrucciones preparadas. Los datos se pasan como una matriz al método insertTransaction
.
submitTransaction
: este método confirma los cambios de forma permanente en la base de datos al emitir un comando commit()
. Sin embargo, si hay un error y las transacciones tienen un problema, el método invoca el método rollBack()
para hacer que la base de datos regrese a su estado original en caso de que se produzca una excepción de PDO.
Su clase DBTransaction
inicia una transacción, prepara los diferentes comandos SQL que se ejecutarán y, por último, confirma los cambios en la base de datos de forma atómica si no hay problemas. De lo contrario, la transacción se revierte. Además, la clase le permite obtener el registro order_id
que acaba de crear al acceder a la propiedad pública last_insert_id
.
Ahora, la clase DBTransaction
está lista para invocarse y utilizarse mediante cualquier código PHP, que se creará a continuación.
Creará una secuencia de comandos PHP que implementará la clase DBTransaction
y enviará un conjunto de comandos SQL a la base de datos de MySQL. Simulará el flujo de trabajo de un pedido de un cliente en un carrito de compras en línea.
Estas consultas SQL afectarán las tablas
orders y orders_products
. Su clase DBTransaction
solo debería permitir cambios en la base de datos si todas las consultas se ejecutan sin errores. De lo contrario, verá un error y se revertirán los cambios que haya intentado implementar.
Creará un pedido único para el cliente JOHN DOE
identificado con la customer_id 1
. En el pedido del cliente hay tres artículos diferentes con cantidades distintas de la tabla products
. Su secuencia de comandos PHP toma los datos del pedido del cliente y los envía a la clase DBTransaction
.
Cree el archivo orders.php
:
- sudo nano /var/www/html/orders.php
Luego, añada el siguiente código al archivo:
<?php
require("DBTransaction.php");
$db_host = "database_host";
$db_name = "database_name";
$db_user = "database_user";
$db_password = "PASSWORD";
$customer_id = 2;
$products[] = [
'product_id' => 1,
'price' => 25.50,
'quantity' => 1
];
$products[] = [
'product_id' => 2,
'price' => 13.90,
'quantity' => 3
];
$products[] = [
'product_id' => 3,
'price' => 45.30,
'quantity' => 2
];
$transaction = new DBTransaction($db_host, $db_user, $db_password, $db_name);
Creó una secuencia de comandos PHP que inicia una instancia de la clase DBTransaction
que creó en el paso 2.
En esta secuencia de comandos, incluye el archivo DBTransaction.php
e inicia la clase DBTransaction
. Luego, prepara una matriz multidimensional con todos los productos que el cliente pide a la tienda. También invoca el método startTransaction()
para iniciar una transacción.
A continuación, añada el siguiente código para finalizar su secuencia de comandos orders.php
. . .
$order_query = "insert into orders (order_id, customer_id, order_date, order_total) values(:order_id, :customer_id, :order_date, :order_total)";
$product_query = "insert into orders_products (order_id, product_id, price, quantity) values(:order_id, :product_id, :price, :quantity)";
$transaction->insertQuery($order_query, [
'customer_id' => $customer_id,
'order_date' => "2020-01-11",
'order_total' => 157.8
]);
$order_id = $transaction->last_insert_id;
foreach ($products as $product) {
$transaction->insertQuery($product_query, [
'order_id' => $order_id,
'product_id' => $product['product_id'],
'price' => $product['price'],
'quantity' => $product['quantity']
]);
}
$result = $transaction->submit();
if ($result) {
echo "Records successfully submitted";
} else {
echo "There was an error.";
}
Guarde y cierre el archivo pulsando CTRL
+ X
, Y
e INTRO
.
El comando que se insertará en la tabla de pedidos se prepara con el método insertTransaction
. A continuación, obtenga el valor de la propiedad pública last_insert_id
de la clase DBTransaction
y úselo como $order_id
.
Una vez que se dispone de una $order_id
, se utiliza la identificación única para insertar los artículos del pedido del cliente en la tabla orders_products
.
Por último, invoque el método submitTransaction
para confirmar los datos del pedido del cliente en la base de datos si no hay problemas. De lo contrario, el método submitTransaction
revertirá los cambios que haya intentado realizar.
Ahora, ejecute la secuencia de comandos orders.php
en su navegador. Ejecute lo siguiente y sustituya your-server-IP
por la dirección IP pública de su servidor:
http://your-server-IP/orders.php
Verá la confirmación de que los registros se enviaron correctamente:
Su secuencia de comandos PHP funciona según lo previsto y el pedido y los productos del pedido se enviaron a la base de datos de forma atómica.
Ejecutó el archivo orders.php
en la ventana de un navegador. La secuencia de comandos invocó la clase DBTransaction
, que, a su vez, envió los datos de orders
a la base de datos. En el siguiente paso, verificará si los registros se almacenaron en las tablas relacionadas de la base de datos.
En este paso, verificará si la transacción del pedido del cliente iniciada desde la ventana del navegador se envió a las tablas de la base de datos según lo previsto.
Para hacerlo, vuelva a iniciar sesión en su base de datos de MySQL:
- sudo mysql -u sample_user -p
Ingrese la contraseña de sample_user
y presione INTRO
para continuar.
Pase a la base de datos sample_store
:
- USE sample_store;
Antes de continuar, asegúrese de que la base de datos se haya modificado al confirmar el siguiente resultado:
OutputDatabase Changed.
Luego, emita el siguiente comando para obtener los registros de la tabla orders
:
- SELECT * FROM orders;
Esto mostrará el siguiente resultado en el que se detalla el pedido del cliente:
Output+----------+---------------------+-------------+-------------+
| order_id | order_date | customer_id | order_total |
+----------+---------------------+-------------+-------------+
| 1 | 2020-01-11 00:00:00 | 2 | 157.8 |
+----------+---------------------+-------------+-------------+
1 row in set (0.00 sec)
A continuación, obtenga los registros de la tabla orders_products
:
- SELECT * FROM orders_products;
Verá un resultado similar al siguiente con una lista de los productos del pedido del cliente:
Output+--------+----------+------------+-------+----------+
| ref_id | order_id | product_id | price | quantity |
+--------+----------+------------+-------+----------+
| 1 | 1 | 1 | 25.5 | 1 |
| 2 | 1 | 2 | 13.9 | 3 |
| 3 | 1 | 3 | 45.3 | 2 |
+--------+----------+------------+-------+----------+
3 rows in set (0.00 sec)
El resultado confirma que la transacción se guardó en la base de datos y que su clase auxiliar DBTransaction
está funcionando según lo previsto.
En esta guía, utilizó PDO de PHP para trabajar con transacciones de MySQL. Si bien en este artículo no se abarcan todos los aspectos del diseño de un software de comercio electrónico, se ofreció un ejemplo para usar transacciones de MySQL en sus aplicaciones.
Para obtener más información sobre el modelo ACID de MySQL, considere consultar la guía InnoDB y el modelo ACID en el sitio web oficial de MySQL. Visite nuestra página de contenido de MySQL para acceder a más tutoriales, artículos y preguntas y respuestas relacionados.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!