Olá pessoal,
No artigo de hoje irei comentar sobre um assunto que representa uma dúvida muito comum para quem vai criar tabelas no Banco de Dados (BD) Oracle: "Qual tipo de dado devo usar: CHAR, VARCHAR ou VARCHAR2?". Essa dúvida surge, normalmente, quando um Desenvolvedor precisa criar colunas para armazenar texto (caracteres alfanuméricos). Para ter a resposta, precisamos entender primeiro as diferenças entre os tipos de dados CHAR, VARCHAR e VARCHAR2:
1- VARCHAR
Atualmente (até a versão 11G do Oracle Database), VARCHAR nada mais é do que um sinônimo para VARCHAR2 (desde o Oracle8), por isso a Oracle recomenda não utilizar este tipo de dado. Existe a possibilidade de VARCHAR ser utilizado em versões futuras para ter uma semântica ou característica diferente de VARCHAR2, mas mesmo, estando tudo igual atualmente, utilize VARCHAR2 ao invés de VARCHAR, para evitar possíveis problemas futuros.
2- VARCHAR2
VARCHAR2 armazena caracteres alfanuméricos de tamanho variável, entre 1 e 4000 bytes ou caracteres. O tamanho padrão desta coluna é especificado em bytes. Agora vem a pergunta, qual a diferença entre armazenar bytes ou caracteres? Bom... quando utilizamos caracteres multibyte, como por exemplo UTF-8 (para representar caracteres específicos em múltiplas linguagens), 1 único caractere pode ser armazenado em até 3 bytes. Nestes casos, uma palavra contendo caracteres especiais, como por exemplo, FÁBIO, poderá ter mais bytes do que caracteres (a letra Á, internamente será armazenada em 2 ou 3 bytes), por isso, recomenda-se, nestes casos, especificar o armazenamento da coluna em caracteres, ao invés de bytes.
3- CHAR
A única diferença entre CHAR e VARCHAR2 é que CHAR armazena caracteres alfanuméricos de tamanho fixo, entre 1 e 2000 bytes ou caracteres. Se você armazenar o sobrenome PRADO em uma coluna CHAR(50), a coluna conterá a string PRADO + 45 espaços em branco que são adicionados automaticamente para preencher o tamanho total da coluna.
Agora que já sabemos as diferenças e particularidades de cada tipo de dados, vamos à pergunta que é o título deste artigo: Qual tipo de dado devo usar: CHAR, VARCHAR ou VARCHAR2?
Resposta: Sabendo que VARCHAR é um sinônimo para VARCHAR2 e que a própria Oracle não recomenda utilizar este tipo de dados, esqueça que ele existe, utilize VARCHAR2 ao invés de VARCHAR. Agora que já sabemos que não devemos utilizar VARCHAR, sobraram apenas 2 tipos de dados para comparar: CHAR e VARCHAR2. Thomas Kyte, um dos maiores especialistas em Oracle do mundo, recomenda evitar CHAR em seu site ASK TOM (ver referências). Resumindo o que ele explica, em termos de armazenamento CHAR nada mais é do que um VARCHAR2 com espaços em branco adicionais. Neste caso, se você cria uma coluna com CHAR ou VARCHAR2 de tamanho 50 e armazena uma palavra que tenha somente 40 caracteres, utilizando VARCHAR2 você poderá economizar espaço em disco e você ainda terá consultas mais rápidas.
Diante de todos estes dados e informações, podemos chegar à conclusão de que devemos evitar CHAR e VARCHAR, utilizando sempre VARCHAR2 para armazenar valores alfanuméricos de até 4000 caracteres (limite de tamanho do Oracle Database 11G).
Segue abaixo um conjunto de scripts para serem executados no SQL Developer (ou em sua ferramenta preferida), para permitir a comparação de performance de INSERTs e SELECTs entre uma tabela com 1 coluna VARCHAR2 e outra tabela idêntica com 1 coluna CHAR. O script do Passo 4 permite comparar a performance de inserções nas 2 tabelas. Os scripts dos Passos 6 e 7 permitem comparar a performance de consultas e os scripts dos Passos 8 e 9 permitem verificar a qtde. de bytes que foram armazenados em cada coluna:
Passo 1: Criando as tabelas de origem de dados:
CREATE TABLE CLIENTE_CHAR (ID_CLIENTE NUMBER, NM_CLIENTE CHAR(50), NM_ENDERECO CHAR(100));
CREATE TABLE CLIENTE_VARCHAR2 (ID_CLIENTE NUMBER, NM_CLIENTE VARCHAR2(50), NM_ENDERECO VARCHAR2(100));
Passo 2: Populando as tabelas de origem de dados:
SET SERVEROUTPUT ON
DECLARE
V_COUNT NUMBER;
BEGIN
FOR V_COUNT IN 1..99999
LOOP
-- executa insert c/ loop em cursor
INSERT INTO CLIENTE_CHAR (ID_CLIENTE, NM_CLIENTE, NM_ENDERECO)
VALUES (V_COUNT, 'NOME ' || TO_CHAR(V_COUNT), 'ENDERECO ' || TO_CHAR(V_COUNT));
INSERT INTO CLIENTE_VARCHAR2 (ID_CLIENTE, NM_CLIENTE, NM_ENDERECO)
VALUES (V_COUNT, 'NOME ' || TO_CHAR(V_COUNT), 'ENDERECO ' || TO_CHAR(V_COUNT));
END LOOP;
COMMIT;
END;
Passo 3: Criando as tabelas teste para comparar performance:
CREATE TABLE CLIENTE_VARCHAR2_B AS SELECT * FROM CLIENTE_VARCHAR2 WHERE 1=2;
CREATE TABLE CLIENTE_CHAR_B AS SELECT * FROM CLIENTE_CHAR WHERE 1=2;
Passo 4: Calculando o tempo de INSERT nas tabelas teste a partir da leitura das tabelas de origens de dados:
SET SERVEROUTPUT ON
DECLARE
L_START NUMBER;
BEGIN
L_START := DBMS_UTILITY.GET_TIME;
-- executa insert c/ loop em cursor
INSERT INTO CLIENTE_CHAR_B
SELECT * FROM CLIENTE_CHAR;
DBMS_OUTPUT.PUT_LINE('Tempo total de INSERT na tabela CLIENTE_CHAR2 : ' || ROUND((DBMS_UTILITY.GET_TIME - L_START)/100,2) || 's');
L_START := DBMS_UTILITY.GET_TIME;
INSERT INTO CLIENTE_VARCHAR2_B
SELECT * FROM CLIENTE_VARCHAR2;
DBMS_OUTPUT.PUT_LINE('Tempo total de INSERT na tabela CLIENTE_VARCHAR2 : ' || ROUND((DBMS_UTILITY.GET_TIME - L_START)/100,2) || 's');
END;
Passo 5: Coletando estatísticas das tabelas teste para gerar plano de execução otimizado:
ANALYZE TABLE CLIENTE_VARCHAR2_B COMPUTE STATISTICS;
ANALYZE TABLE CLIENTE_CHAR_B COMPUTE STATISTICS;
Passo 6: Verificando tempo de consulta na tabela teste que tem colunas CHAR:
EXPLAIN PLAN FOR
SELECT * FROM CLIENTE_CHAR_B WHERE NM_CLIENTE = 'NOME 1'; --
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
Passo 7: Verificando tempo de consulta na tabela teste que tem colunas VARCHAR2:
EXPLAIN PLAN FOR
SELECT * FROM CLIENTE_VARCHAR2_B WHERE NM_CLIENTE = 'NOME 1'; --
select * from table(DBMS_XPLAN.DISPLAY);
Passo 8: Verificando qtde de bytes armazenados na coluna nm_cliente da tabela teste com CHAR:
SELECT DUMP(NM_CLIENTE) FROM CLIENTE_CHAR_B;
Passo 9: Verificando qtde. de bytes armazenados na coluna nm_cliente da tabela teste com VARCHAR2:
SELECT DUMP(NM_CLIENTE) FROM CLIENTE_VARCHAR2_B;
Obs.: Após efetuar os testes de comparação, execute os comandos abaixo para apagar as tabelas criadas nos passos anteriores:
DROP TABLE CLIENTE_VARCHAR2 PURGE;
DROP TABLE CLIENTE_VARCHAR2_B PURGE;
DROP TABLE CLIENTE_CHAR PURGE;
DROP TABLE CLIENTE_CHAR_B PURGE;
COMENTÁRIOS FINAIS:
Em testes que eu fiz, o tempo de consulta na tabela que tem a coluna VARCHAR2 foi 4.6 vezes mais rápido e o tempo de inserção na tabela que tem a coluna VARCHAR2 foi 3.5 vezes mais rápido.
CONCLUSÃO:
Evite CHAR ou VARCHAR. Não utilize CHAR nem para criar colunas que armazenarão apenas 1 caractere de tamanho fixo. Atualmente não há vantagens em utilizar este tipo de dado. Utilize sempre VARCHAR2. Este sim poderá oferecer vantagens de performance e armazenamento quando a definição do tamanho da coluna for maior que o valor armazenado.