Criar usuário Oracle: CDB, PDB e non-CDB sem erro
Antes do CREATE USER no Oracle 19c, três perguntas definem tudo. Veja o ORA-01045, a diferença entre usuário comum (C##) e local, e o privilégio mínimo.

O pedido costuma chegar curto: "cria um usuário para a aplicação nova". O CREATE USER em si cabe em três linhas e qualquer DBA júnior escreve de cabeça. O que complica vem depois, quando o usuário não consegue conectar, ou conecta onde não devia, ou existe no root de um multitenant e ninguém sabe explicar por quê.
Criar usuário no Oracle é simples. Criar certo depende de contexto, e o contexto cabe em três perguntas que eu respondo antes de digitar qualquer comando.
As três perguntas antes do CREATE USER
- O ambiente é CDB ou non-CDB?
- Onde o usuário vai ter acesso: uma PDB, várias, todas, ou um banco tradicional?
- Quais privilégios ele precisa?
A primeira muda o comando. Num CDB, o lugar onde você está conectado define que tipo de usuário nasce. Num non-CDB (a arquitetura tradicional, que existia antes do 12c) não há essa escolha, e o comportamento é o mesmo de criar o usuário dentro de uma PDB.
A segunda define o escopo: um usuário que só precisa de uma PDB não deveria existir nas outras.
A terceira é a que mais dá trabalho, porque quem pede o acesso muitas vezes não sabe dizer o que precisa. Cabe ao DBA conversar até chegar no privilégio mínimo, e começar sempre do menor conjunto possível.
Criando o usuário em um non-CDB
No lab, o banco ORCL é non-CDB. Conectado como SYS:
create user app1 identified by oracle
default tablespace users
temporary tablespace temp;
A senha aí é a do laboratório. Em ambiente real, use uma senha gerada e entregue por um canal adequado.
Para conferir que o usuário existe:
set lines 200
set pages 200
col username format a20
col account_status format a20
select username as username,
account_status as account_status
from dba_users
where username = 'APP1';
O status vem como OPEN, o que dá a impressão de que o usuário está pronto. Não está. Saia do SQL*Plus e tente conectar com ele:
$ sqlplus app1/oracle
ORA-01045: user APP1 lacks CREATE SESSION privilege; logon denied
Esse é o comportamento padrão: um usuário recém-criado não tem privilégio nenhum, nem o de abrir uma sessão. O mínimo para ele conectar é o CREATE SESSION:
grant create session to app1;
Agora a conexão funciona, e dá para ver exatamente o que a sessão carrega:
show user
select privilege as privilege
from session_privs;
A SESSION_PRIVS lista os privilégios ativos na sessão atual. Para o APP1 aparece só o CREATE SESSION, e é isso que eu quero ver num usuário de aplicação recém-entregue. Tudo que vier além disso precisa de uma razão.
O roteiro completo com todos os comandos deste artigo está em https://mrdba.com.br/labs/criar-usuarios-oracle?utm_source=blog&utm_medium=artigo&utm_campaign=criar-usuarios-oracle. Dá para rodar do começo ao fim na sua própria máquina.
Usuário comum no CDB: o prefixo C## e o CONTAINER = ALL
Num CDB existem dois tipos de usuário, e a diferença é de escopo. O usuário comum é criado no CDB$ROOT e existe no root e em todas as PDBs. O nome precisa começar com C##.
No lab, o CDB1 tem as PDBs HUMAN_RESOURCES e CUSTOMER_ORDERS. Antes de criar um usuário comum, confira o estado delas:
show pdbs
Se alguma estiver MOUNTED, abra antes com alter pluggable database <nome> open;. Assim o usuário comum aparece em todas as PDBs já na consulta de conferência logo abaixo (uma PDB fechada só recebe o usuário quando for aberta).
Conectado no CDB$ROOT:
create user c##app identified by oracle container = all;
grant create session to c##app container = all;
grant set container to c##app container = all;
O container = all aparece nas três linhas por um motivo. Na criação, diz que o usuário vale para todos os containers. Nos grants, diz que o privilégio também vale em todos. Sem ele no grant, o usuário comum existiria nas PDBs mas só poderia conectar no root.
A cláusula CONTAINER só aceita ALL ou CURRENT. Não existe uma forma de dizer "este usuário comum em duas das cinco PDBs". Se o escopo é parcial, a resposta é outra: usuário local, que vem na próxima seção.
Sobre o grant set container: ele serve para trocar de container dentro da sessão, com alter session set container = <pdb>. Conectar direto numa PDB pelo serviço dela já funciona só com o CREATE SESSION concedido com container = all. Conceda o SET CONTAINER só quando o usuário realmente precisa navegar entre PDBs.
Para confirmar onde o usuário existe:
col common format a6
col name format a20
select b.name as pdb,
a.username as username,
a.common as common
from cdb_users a, v$pdbs b
where a.con_id = b.con_id
and a.username like 'C##APP'
order by b.name;
A CDB_USERS enxerga todos os containers de uma vez, e o join com V$PDBS traz o nome de cada PDB. O resultado é uma linha por PDB, com COMMON igual a YES. Sem o join, aparece também a linha do root (CON_ID 1).
Usuário local: criado dentro da PDB, sem prefixo
O usuário local pertence a uma única PDB. Não leva prefixo e se comporta igual a um usuário de non-CDB, porque do ponto de vista da PDB ela é um banco completo.
Para criar o mesmo usuário em duas PDBs, entre em cada uma:
alter session set container = HUMAN_RESOURCES;
create user app_local identified by oracle;
alter session set container = CUSTOMER_ORDERS;
create user app_local identified by oracle;
Antes de cada CREATE USER, show con_name confirma em qual container você está. Eu rodo esse comando por hábito, para não criar o usuário na PDB errada.
Os dois APP_LOCAL têm o mesmo nome e a mesma senha, mas são dois usuários diferentes. Cada um vive no dicionário da sua PDB, com seus próprios privilégios e objetos. Dropar um não afeta o outro.
O usuário local também nasce sem CREATE SESSION, então cai no mesmo ORA-01045 até receber o grant dentro da PDB.
| Usuário comum | Usuário local | |
|---|---|---|
| Onde é criado | CDB$ROOT | Dentro da PDB |
| Prefixo | C## obrigatório | Sem prefixo |
| Onde existe | Root e todas as PDBs | Só na PDB onde foi criado |
| Cláusula | CONTAINER = ALL | CONTAINER = CURRENT (padrão) |
Por que o prefixo C## existe
O prefixo existe para evitar conflito de nomes entre o root e as PDBs. Imagine que o root aceitasse um usuário comum chamado APP e, em paralelo, alguém criasse um usuário local APP dentro de uma PDB. Na hora de propagar o comum para aquela PDB, o nome já estaria ocupado.
Com a regra do C##, os dois espaços de nomes ficam separados. O root só aceita nomes com C## para usuários comuns, e uma PDB não aceita C## para usuários locais. Se você tentar criar APP direto no root, o Oracle recusa com ORA-65096. Se tentar criar C##APP dentro da PDB, recusa também.
Consultando os usuários no dia a dia
DBA_USERS mostra os usuários do container em que você está conectado, e CDB_USERS, consultada no root, mostra todos os containers. Eu guardo uma consulta sobre a DBA_USERS num script no SQLPATH, que filtra por parte do nome e traz criação, profile, status, tablespaces e as colunas COMMON e ORACLE_MAINTAINED. A segunda ajuda a separar o que você criou do que o Oracle trouxe na instalação. O script completo está no lab.
Boas práticas para criar usuário no Oracle
Defina o escopo antes de criar. Se a pessoa precisa de acesso somente na CUSTOMER_ORDERS, crie um usuário local nessa PDB e pare aí. Um usuário comum com CONTAINER = ALL é a escolha certa para quem administra o CDB inteiro, e é exagero para uma aplicação que vive numa PDB só.
Conceda só o CREATE SESSION no começo e acrescente o resto conforme o uso aparecer. Na prática o conjunto mínimo costuma crescer, mas é bem mais fácil justificar um privilégio adicionado do que descobrir depois por que um usuário de relatório podia alterar tabelas.
Quando o ambiente for multitenant, confirme o container antes do comando. O mesmo CREATE USER, rodado no root ou dentro de uma PDB, produz usuários de tipos diferentes.
Se você quer montar a base de conhecimento por trás de decisões como essa, o e-book Raízes de um DBA organiza o caminho: https://mrdba.com.br/roteiro-dba.
Marcio Mandarino é DBA Oracle e SQL Server há mais de 20 anos, com passagens por ambientes críticos de varejo, saúde, educação e locação de equipamentos.
Marcio Mandarino
DBA Oracle e SQL Server há mais de 20 anos. Atendo plantão, recuperação de incidente e gestão contínua de bancos de dados. Este artigo saiu de um laboratório que rodou antes de virar texto.

