O autor selecionou a Open Sourcing Mental Illness para receber uma doação como parte do programa Write for DOnations.

Introdução

Uma transação de MySQL consiste em um grupo de comandos em SQL relacionados de maneira lógica e que são executados no banco de dados como uma única unidade. As transações são usadas para aplicar a conformidade com as propriedades ACID (Atomicidade, Consistência, Isolamento e Durabilidade) em um aplicativo. Este é um conjunto de padrões que regem a confiabilidade das operações de processamento em um banco de dados.

A atomicidade assegura o êxito de transações relacionadas ou um fracasso completo se ocorrer um erro. A consistência garante a validade dos dados enviados ao banco de dados, de acordo com a lógica de negócios definida. O isolamento consiste na execução correta de transações concomitantes, garantindo que os efeitos de diferentes clientes conectados a um banco de dados não afetem uns aos outros. A durabilidade garante que transações logicamente relacionadas permaneçam no banco de dados permanentemente.

As instruções de SQL emitidas através de uma transação devem ser bem-sucedidas ou falhar completamente. Se alguma das consultas falhar, o MySQL reverte as alterações e elas nunca são confirmadas no banco de dados.

Um bom exemplo para entender como as transações do MySQL funcionam é um site de e-commerce. Quando um cliente realiza um pedido, o aplicativo insere registros em várias tabelas, como: orders (pedidos) e orders_products (produtos do pedido), dependendo da lógica de negócios. Os registros de múltiplas tabelas relacionadas a um único pedido precisam ser enviados atomicamente ao banco de dados como uma única unidade lógica.

Outro caso de uso está em um aplicativo bancário. Quando um cliente estiver transferindo dinheiro, algumas transações são enviadas ao banco de dados. A conta do remetente é debitada e a conta do destinatário é creditada. As duas transações devem ser confirmadas simultaneamente. Se uma delas falhar, o banco de dados retornará ao seu estado original e nenhuma alteração deve ser salva no disco.

Neste tutorial, você usará a extensão PDO do PHP. Ela oferece uma interface para trabalhar com bancos de dados em PHP, possibilitando a realização de transações do MySQL em um servidor Ubuntu 18.04.

Pré-requisitos

Antes de começar, você precisará do seguinte:

Passo 1 — Criando um banco de dados amostrais e tabelas

Antes de começar a trabalhar com transações do MySQL, primeiro você criará um banco de dados amostrais e adicionará algumas tabelas. Primeiro, faça login no seu servidor do MySQL como root:

  • sudo mysql -u root -p

Quando solicitado, digite sua senha de user root do MySQL e clique em ENTER para continuar. Em seguida, crie um banco de dados. Para os fins deste tutorial, chamaremos o banco de dados de sample_store:

  • CREATE DATABASE sample_store;

Você verá o seguinte resultado:

Output
Query OK, 1 row affected (0.00 sec)

Crie um usuário chamado sample_user para o seu banco de dados. Lembre-se de substituir a PASSWORD por um valor forte:

  • CREATE USER 'sample_user'@'localhost' IDENTIFIED BY 'PASSWORD';

Emita privilégios completos para o seu usuário no banco de dados sample_store:

  • GRANT ALL PRIVILEGES ON sample_store.* TO 'sample_user'@'localhost';

Por fim, recarregue os privilégios do MySQL:

  • FLUSH PRIVILEGES;

Você verá o seguinte resultado assim que criar seu usuário:

Output
Query OK, 0 rows affected (0.01 sec) . . .

Com o banco de dados e o usuário configurados, agora você pode criar várias tabelas para demonstrar como as transações do MySQL funcionam.

Faça log-off do servidor MySQL:

  • QUIT;

Assim que o sistema desconectar você, verá o seguinte resultado:

Output
Bye.

Em seguida, faça login com as credenciais do sample_user que você acabou de criar:

  • sudo mysql -u sample_user -p

Digite a senha para o sample_user e clique em ENTER para continuar.

Vá para o sample_store, fazendo dele banco de dados selecionado no momento:

  • USE sample_store;

Você verá o seguinte resultado assim que ele for selecionado:

Output
Database Changed.

Em seguida, crie uma tabela de products (produtos):

  • CREATE TABLE products (product_id BIGINT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(50), price DOUBLE) ENGINE = InnoDB;

Esse comando cria uma tabela products com um campo chamado product_id. Voce usará um tipo de dados chamado BIGINT que consegue acomodar um valor elevado de até 2^63-1. Esse mesmo campo é usado como uma PRIMARY KEY, de modo a identificar exclusivamente produtos. A palavra-chave AUTO_INCREMENT instrui o MySQL a gerar o próximo valor numérico conforme novos produtos vão sendo adicionados.

O campo product_name é do tipo VARCHAR, que consegue reter um máximo de 50 letras ou números. Para o price (preço) do produto, usa-se um tipo de dados chamado DOUBLE para suprir os formatos com ponto flutuante, em preços com casas decimais.

Por fim, usa-se o InnoDB como ENGINE, pois ele suporta com facilidade as transações do MySQL, ao contrário de outros mecanismos de armazenamento, como o MyISAM.

Assim que criar sua tabela de products, você receberá o seguinte resultado:

Output
Query OK, 0 rows affected (0.02 sec)

Em seguida, adicione alguns itens à tabela de products, executando os comandos a seguir:

  • 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');

Após cada operação de INSERT, você verá um resultado parecido com este:

Output
Query OK, 1 row affected (0.02 sec) . . .

Em seguida, verifique se os dados foram adicionados à tabela de produtos:

  • SELECT * FROM products;

Você verá uma lista dos quatro produtos que você inseriu:

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)

Em seguida, você criará uma tabela de customers (clientes) para reter informações básicas sobre os clientes:

  • CREATE TABLE customers (customer_id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_name VARCHAR(50) ) ENGINE = InnoDB;

Assim como na tabela de products, você usa o tipo de dados BIGINT para o customer_id. Isso garantirá que a tabela possa suportar muitos clientes, indo até 2^63-1 registros. A palavra-chave AUTO_INCREMENT aumenta o valor das colunas assim que você insere um novo cliente.

Como a coluna customer_name aceita valores alfanuméricos, usa-se o tipo de dados VARCHAR com um limite de 50 caracteres. Novamente, o ENGINE de armazenamento InnoDB é usado para dar suporte às transações.

Após executar o comando anterior para criar a tabela customers, você verá o seguinte resultado:

Output
Query OK, 0 rows affected (0.02 sec)

Você adicionará três clientes da amostra à tabela. Execute os seguintes comandos:

  • INSERT INTO customers(customer_name) VALUES ('JOHN DOE');
  • INSERT INTO customers(customer_name) VALUES ('ROE MARY');
  • INSERT INTO customers(customer_name) VALUES ('DOE JANE');

Assim que os clientes tiverem sido adicionados, você verá um resultado parecido com este:

Output
Query OK, 1 row affected (0.02 sec) . . .

Em seguida, verifique os dados na tabela customers:

  • SELECT * FROM customers;

Você verá uma lista dos três clientes:

Output
+-------------+---------------+ | customer_id | customer_name | +-------------+---------------+ | 1 | JOHN DOE | | 2 | ROE MARY | | 3 | DOE JANE | +-------------+---------------+ 3 rows in set (0.00 sec)

Em seguida, você criará uma tabela de orders para registrar os pedidos realizados por diferentes clientes. Para criar a tabela de orders, execute o seguinte comando:

  • CREATE TABLE orders (order_id BIGINT AUTO_INCREMENT PRIMARY KEY, order_date DATETIME, customer_id BIGINT, order_total DOUBLE) ENGINE = InnoDB;

Você usará a coluna order_id como a PRIMARY KEY. O tipo de dados BIGINT permite que você acomode até 2^63-1 pedidos e aumentará automaticamente o valor após cada inserção de pedido. O campo order_date terá a data e hora de quando o pedido foi realizado. Consequentemente, o tipo de dados DATETIME é usado. O customer_id se relaciona à tabela de customers que você criou anteriormente.

Você verá o seguinte resultado:

Output
Query OK, 0 rows affected (0.02 sec)

Como um único pedido de um cliente pode conter vários itens, você precisa criar uma tabela de orders_products para reter essas informações.

Para criar a tabela de orders_products, execute o seguinte comando:

  • CREATE TABLE orders_products (ref_id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT, product_id BIGINT, price DOUBLE, quantity BIGINT) ENGINE = InnoDB;

Você deve usar o ref_id como a PRIMARY KEY e isso aumentará o valor automaticamente após cada inserção de registro. A order_id e a product_id se relacionam às tabelas de orders e products, respectivamente. A coluna de price é do tipo de dados DOUBLE, para que possa acomodar valores com pontos flutuantes.

O mecanismo de armazenamento InnoDB deve corresponder às outras tabelas criadas anteriormente, uma vez que o pedido de um único cliente afetará várias tabelas que usam simultaneamente as transações.

Seu resultado confirmará a criação da tabela:

Output
Query OK, 0 rows affected (0.02 sec)

Você não adicionará dados às tabelas de orders e orders_products por enquanto. No entanto, você fará isso mais tarde, usando um script PHP que implementa as transações do MySQL.

Faça log-off do servidor MySQL:

  • QUIT;

Agora, seu esquema de banco de dados está completo e você o preencheu com alguns registros. Em seguida, você criará uma classe PHP para lidar com conexões de banco de dados e transações do MySQL.

Passo 2 — Projetando uma classe PHP para lidar com as transações do MySQL

Neste passo, você criará uma classe PHP que usará PDO (Objetos de dados do PHP) para lidar com as transações do MySQL. A classe irá se conectar ao seu banco de dados MySQL e inserirá dados atomicamente ao banco de dados.

Salve o arquivo de classe no diretório raiz do seu servidor Web Apache. Para fazer isso, crie um arquivo DBTransaction.php, usando seu editor de texto:

  • sudo nano /var/www/html/DBTransaction.php

Depois, adicione o código a seguir ao arquivo: Substitua a PASSWORD pelo valor que você criou no Passo 1:

/var/www/html/DBTransaction.php
<?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);
    }

No início da classe DBTransaction, o PDO usará as constantes (DB_HOST, DB_NAME, DB_USER e DB_PASSWORD) para inicializar e se conectar ao banco de dados que você criou no passo 1.

Nota: como estamos demonstrando aqui transações do MySQL em uma escala pequena, declaramos as variáveis do banco de dados na classe DBTransaction. Em um projeto de produção grande, normalmente, você criaria um arquivo de configuração separado e carregaria as constantes do banco de dados desse arquivo usando uma instrução PHP require_once.

Em seguida, você deve definir dois atributos para a classe PDO:

  • ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION: este atributo instruirá o PDO a gerar uma exceção se um erro for encontrado. Tais erros podem ser registrados para depuração.
  • ATTR_EMULATE_PREPARES, false: esta opção desabilita a emulação das instruções preparadas e permite que o próprio mecanismo de banco de dados MySQL prepare as instruções.

Agora, adicione o seguinte código ao seu arquivo para criar os métodos para a sua classe:

/var/www/html/DBTransaction.php
. . .
    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;
    }
}

Salve e feche o arquivo, pressionando CTRL + X, Y e, depois, ENTER.

Para trabalhar com as transações do MySQL, criam-se três métodos principais na classe DBTransaction: startTransaction, insertTransaction e submitTransaction.

  • startTransaction: este método instrui o PDO a iniciar uma transação e desliga a confirmação automática até que um comando de confirmação seja emitido.

  • insertTransaction: este método recebe dois argumentos. A variável $sql mantém a instrução SQL a ser executada, enquanto a variável $data é uma matriz dos dados a serem ligados à instrução SQL, uma vez que você está usando instruções preparadas. Os dados são informados como uma matriz ao método insertTransaction.

  • submitTransaction: este método confirma as alterações no banco de dados permanentemente, emitindo um comando de commit(). No entanto, se houver um erro e as transações tiverem um problema, o método chama o método rollBack() para reverter o banco de dados ao seu estado original, caso uma exceção do PDO seja levantada.

Sua classe DBTransaction inicializa uma transação, prepara os diferentes comandos SQL a serem executados. Por fim, confirma atomicamente as alterações no banco de dados se não houver problemas. Caso contrário, a transação é revertida. Além disso, a classe permite que você recupere o registro order_id que você acabou de criar, acessando a propriedade pública last_insert_id.

Agora, a classe DBTransaction está pronta para ser chamada e usada por qualquer código PHP, que você criará a seguir.

Passo 3 — Criando um script PHP para usar a classe DBTransaction

Você criará um script PHP que implementará a classe DBTransaction e enviará um grupo de comandos SQL ao banco de dados MySQL. Você imitará o fluxo de trabalho de um pedido de cliente em um carrinho de compras on-line.

Essas consultas SQL afetarão as orders e as tabelas de orders_products. Sua classe DBTransaction deve permitir alterações no banco de dados apenas se todas as consultas forem executadas sem erros. Caso contrário, você receberá um erro e qualquer tentativa de alteração será revertida.

Você está criando um único pedido para o cliente JOHN DOE, identificado com o customer_id 1. O pedido do cliente possui três itens diferentes com quantidades diferentes na tabela de products. Seu script PHP recebe os dados do pedido do cliente e os envia à classe DBTransaction.

Crie o arquivo orders.php:

  • sudo nano /var/www/html/orders.php

Depois, adicione o código a seguir ao arquivo:

/var/www/html/orders.php
<?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);

Você criou um script PHP que inicializa uma instância da classe DBTransaction que você criou no Passo 2.

Nesse script, você inclui o arquivo DBTransaction.php e inicializa a classe DBTransaction. Em seguida, prepare uma matriz multidimensional de todos os produtos que o cliente está pedindo da loja. Você também invocará o método startTransaction() para iniciar uma transação.

Em seguida, adicione o código a seguir para finalizar seu script orders.php:

/var/www/html/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.";
}

Salve e feche o arquivo, pressionando CTRL + X, Y e, depois, ENTER.

Prepare o comando a ser inserido na tabela de pedidos através do método insertTransaction. Depois, recupere o valor da propriedade pública last_insert_id da classe DBTransaction e o utilize como $order_id.

Assim que você tiver um $order_id, utilize o ID único para inserir os itens do pedido do cliente na tabela de orders_products.

Por fim, você chama o método submitTransaction para confirmar todos os detalhes do pedido do cliente no banco de dados se não houver problemas. Caso contrário, o método submitTransaction reverterá as tentativas de alterações.

Agora, você executará o script orders.php em seu navegador. Substitua o your-server-IP pelo endereço IP público do seu servidor e execute o seguinte:

http://your-server-IP/orders.php

Você verá uma confirmação de que os registros foram enviados com sucesso:

Resultado em PHP da classe MySQL de transações

Seu script PHP está funcionando conforme o esperado e o pedido - com os produtos do pedido associados - foram enviados atomicamente ao banco de dados.

Você executou o arquivo orders.php em uma janela do navegador. O script invocou a classe DBTransaction que, por sua vez, enviou os detalhes de orders ao banco de dados. No próximo passo, você verificará se os registros foram salvos nas tabelas do banco de dados relacionadas.

Passo 4 — Confirmando as entradas em seu banco de dados

Neste passo, você verificará se a transação iniciada a partir da janela do navegador para o pedido do cliente foi postada nas tabelas do banco de dados, conforme esperado.

Para fazer isso, faça login no seu banco de dados MySQL novamente:

  • sudo mysql -u sample_user -p

Digite a senha para o sample_user e tecle ENTER para continuar.

Vá para o banco de dados sample_store:

  • USE sample_store;

Antes de prosseguir, certifique-se de que o banco de dados foi alterado, confirmando o resultado a seguir:

Output
Database Changed.

Em seguida, emita o seguinte comando para recuperar os registros da tabela orders:

  • SELECT * FROM orders;

Isso exibirá o seguinte resultado, detalhando o pedido do 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)

Em seguida, recupere os registros da tabela orders_products:

  • SELECT * FROM orders_products;

Você verá um resultado com uma lista de produtos do pedido do cliente, parecido com o seguinte:

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)

O resultado confirma que a transação foi salva no banco de dados e que sua classe DBTransaction auxiliar está funcionando conforme o esperado.

Conclusão

Neste guia, você usou o PDO do PHP para trabalhar com as transações do MySQL. Embora este não seja um artigo conclusivo sobre a criação de um software de e-commerce, trata-se de um exemplo de como usar transações do MySQL em seus aplicativos.

Para aprender mais sobre o modelo ACID do MySQL, consulte o guia InnoDB e o modelo ACID, no site oficial do MySQL. Acesse nossa página de conteúdo do MySQL para obter mais tutoriais, artigos e Q&A relacionados.

0 Comments

Creative Commons License