O que é ACID?
Tags: sql, postgres
Published: 2022-07-04 23:25:56

O termo foi criado no ano de 1983, por Andreas Reuter e Theo Harder, no qual visa definir os 4 termos que compõem uma transação em um banco de dados. Atomicidade, Consistência, Isolamento e Durabilidade. 

### *O que é uma transação?*
Uma transação é uma única unidade de trabalho contendo alguma operação do banco de dados como por exemplo o INSERT. Quando iniciamos uma transação, e se por algum motivo uma ou mais operações falharem, nós poderemos fazer o rollback ou seja voltar o banco ao seu estado anterior. Veja o exemplo abaixo:
```
1: CREATE SCHEMA acid;

2: CREATE TABLE acid.herois (
3: 	nome VARCHAR(255) NOT NULL,
4:  	sobrenome VARCHAR(255) NOT NULL,
5:  	UNIQUE(nome, sobrenome)
6: );
7:
8: START TRANSACTION;
9: INSERT INTO acid.herois values('Steve', 'Rogers'), ('Tony', 'Stark');
10: COMMIT;
```

Repare na **linha 8**, é nesse ponto que iniciamos uma transação. Se a operação feita na **linha 9** falhar o commit não será executado e teremos que fazer o rollback.

### Implementando na prática os 4 termos do ACID. 
Antes de partir para a prática, vamos entender os 4 termos do ACID.

**Atomicidade:** Todas as operações incluídas dentro de uma transação devem ser realizadas com sucesso, se por acaso alguma operação falhar o rollback deverá ser feito e o banco voltará ao seu estado anterior. Porém se todas as operações forem efetuadas com sucesso a transação deve ser concluída com o comando COMMIT.  

**Consistência:** Todas as regras e restrições que definimos no banco de dados devem ser obedecidas, como por exemplo, tipo dos campos (Boolean, Char, Varchar, Integer, Date, etc), ou seja eu não posso adicionar um texto em um campo onde seu tipo é Integer, relacionamentos por chaves estrangeiras, ou seja todas essas regras devem ser obedecidas. 

**Isolamento:** Nenhuma transação poderá interferir no funcionamento da outra transação se as duas estiverem rodando em paralelo. E nenhuma transação poderá visualizar os resultados parciais das outras transações. Ou seja, cada transação tem que ser completamente independente, entenderá melhor esse funcionamento mais adiante, porém já adiantando que essa regra irá mudar de acordo com o tipo do SGBD que você utilizará.

**Durabilidade:** O resultado de uma transação são permanentes e só poderão ser alterados por uma transação subsequente.

### Atomicidade

Por padrão a instrução `INSERT` já é atômica, ou seja, eu não preciso adicionar explicitamente o comando `START TRANSACTION` como fizemos acima, para dizer que vou iniciar uma transação. Tente executar esses dois inserts abaixo de forma separada no seu editor de SQL e veja o que acontece. 

```
CREATE TABLE banco (
    nome VARCHAR (50) NOT NULL,
    saldo MONEY NOT NULL
);

INSERT INTO banco (NOME, SALDO) VALUES ('Tim', 100); 

INSERT INTO banco (NOME, SALDO) VALUES ('Tim'); 
```

Veja que o primeiro `INSERT` tudo funcionou, porém já no segundo `INSERT` ocorreu um erro porque estamos tentando adicionar um valor NULL em uma coluna `NOT NULL`, logo o postgresql não vai adicionar apenas um valor no banco se caso o segundo valor a ser adicionado esteja incorreto.

Veja agora esse segundo exemplo.

```
1: DO
2: $$
3: DECLARE 
4: 	I INT;
5: BEGIN
6:    FOR I IN 1..3 LOOP
7:		INSERT INTO banco (NOME, SALDO) VALUES ('Tim', 100);
8:	END LOOP;
9: END; 
10: $$;
11: 
12: DO
13: $$
14: DECLARE 
15:	I INT;
16: BEGIN
17:    FOR I IN 1..3 LOOP
18:		INSERT INTO banco (NOME, SALDO) VALUES ('Martin', 300);
19:	END LOOP;
20: END; 
21: $$;
```
No código acima, fizemos um laço com 3 repetições, e dentro dele temos uma simples instrução `INSERT`.

Após executar o código acima, veja que os valores foram adicionados corretamente e todas as instruções passaram sem maiores problemas. 
Agora apague tudo que foi adicionado na tabela, porque vamos forçar um erro no segundo `INSERT` na linha 18 para ver o que acontece. 

```
1: DO
2: $$
3: DECLARE 
4: 	I INT;
5: BEGIN
6:    FOR I IN 1..3 LOOP
7:		INSERT INTO banco (NOME, SALDO) VALUES ('Tim', 100);
8:	END LOOP;
9: END; 
10: $$;
11: 
12: DO
13: $$
14: DECLARE 
15:	I INT;
16: BEGIN
17:    FOR I IN 1..3 LOOP
18:		INSERT INTO banco (NOME, SALDO) VALUES ('Martin');
19:	END LOOP;
20: END; 
21: $$;
```
Veja que apaguei o saldo da conta do Martin, porém a coluna saldo não aceita valores do tipo `NULL`.

Ao executar esse bloco de código repare houve um erro na linha 18, onde estamos tentando inserir um valor do tipo `NULL` em uma coluna que não permite tais valores, porém se rodarmos o comando `SELECT * FROM banco;` veja que nenhum valor foi adicionado, nem mesmo os valores contidos no `INSERT` da linha 7. Então podemos afirmar que esse conjunto de instruções são atômicas, porque toda a instrução tem que ser bem-sucedida para que os dados sejam adicionados ao banco, se por acaso alguma instrução falhar nada será modificado em nossas tabelas.

Isso acontece pelo seguinte motivo, repare na linha 16 temos a instrução chamada `BEGIN` essa instrução serve para iniciarmos uma transação, e o `COMMIT` para confirmar. Mas repare que eu não adicionei nenhuma instrução `COMMIT` ao código, porque especificamente no postgresql quando se inicia uma transação e não existe uma instrução `COMMIT` ele executa a transação em modo de **auto commit** veja com mais detalhes na [documentação](https://www.postgresql.org/docs/current/sql-begin.html).



### Consistência
O exemplo acima explica bem como funciona a consistência do banco de dados. Repare que não conseguimos adicionar um valor do tipo `NULL` em uma coluna do tipo `MONEY`. Também não é possível adicionar uma palavra qualquer ao invés de um valor monetário.
`INSERT INTO banco (NOME, SALDO) VALUES ('Martin', 'money');`

### Durabilidade
Toda a transação é permanente e só pode ser desfeita por uma outra transação. 
Após o `COMMIT` de uma transação os dados devem ser salvos ou excluídos de forma definitiva no banco de dados, não podendo haver erros por falha de hardware por exemplo. 

### Isolamento
No postgresql existem 4 tipos de isolamento; Read Uncommitted, Read Committed, Repeatable Read, Serializable, mas  por padrão ele utiliza o Read Committed. Com o comando `SET TRANSACTION` podemos alterar esse comportamento.
Para fins didáticos vamos entender apenas o tipo Read Committed.

**read commited**
Esse nível de isolamento funciona da seguinte maneira, por exemplo o comando `SELECT` ele vai ler apenas os dados que foram confirmados. Isso acontece porque o `SELECT` lê um snapshot do banco de dados antes do início da leitura. O comando SELECT também pode ler as alterações que foram feitas dentro da mesma transação, mesmo que não tenham sido confirmadas. Veja o exemplo abaixo. (Ainda não execute a instrução COMMIT).

```
START TRANSACTION;
    INSERT INTO banco (nome, saldo) VALUES ('Mark', '200');
    SELECT * FROM banco;
/*COMMIT*/
```
Veja que o `SELECT` leu os dados que ainda não foram confirmados.

Em seguida, abra mais uma instância do seu editor de SQL e faça uma nova conexão com o banco, depois digite a instrução SQL abaixo, para verificarmos se ele conseguirá ler as informações que adicionamos acima, porém ainda não confirmadas. 
```
SELECT * FROM banco;
```
Veja que nada nos retornou. Agora descomente e execute a instrução `COMMIT` que fizemos acima, e posteriormente rode a instrução `SELECT` que fizemos fora da transação. Tudo funcionou como esperado, após confirmamos a transação os dados foram adicionados no banco e o select que estava fora da transação conseguiu ler os dados que foram inseridos na tabela. 

Mas se tivermos 2 transações em andamento de forma paralela, a primeira transação excluindo linha com o nome Mark já a segunda atualizando essa mesma linha. O Read Committed irá tratá-las da seguinte maneira.
Se temos duas transações rodando em paralelo, se a primeira transação for uma exclusão, a segunda transação irá aguardar a confirmação dos resultados da primeira transação. Se a primeira transação for confirmada, a segunda transação irá ignorar a linha excluída pela primeira transação. Porém se as atualizações forem revogadas a segunda transação irá prosseguir normalmente com as suas atualizações.

#### Referências: 
[Transactions](https://www.postgresql.org/docs/current/tutorial-transactions.html)
[Transacions Isolation](https://www.postgresql.org/docs/current/transaction-iso.html)