Prisma ORM vs Drizzle ORM: comparação técnica e roteiro de benchmark reproduzível

Conteúdo técnico toda semana
Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.
Sem spam. Cancele a qualquer momento com 1 clique.
Neste artigo
Nota de setembro de 2026: a versão anterior deste artigo publicava tabelas de latência, throughput e memória apresentadas como "benchmark real", com números que não podem ser reproduzidos por quem lê (nem por este blog hoje: a máquina, o dataset e os dados brutos daquela rodada não foram preservados). Esses números saíram. O que fica é a comparação técnica, que se sustenta na arquitetura das duas bibliotecas, e um roteiro completo para você medir no seu ambiente. Quando este blog rodar o roteiro num repositório público, com lockfile e dados brutos, os números voltam com a fonte ao lado.
O debate que não para
Prisma ou Drizzle? A pergunta domina fóruns e threads desde 2024, e a maioria das comparações repete opiniões. Este artigo faz outra coisa: explica de onde vem cada diferença de comportamento e entrega o código para você confirmar com o seu banco e o seu workload.
As duas arquiteturas, sem números
Como o Prisma executa uma query
Na configuração clássica, o Prisma Client conversa com um query engine em Rust, que traduz a consulta para SQL e devolve o resultado. Cada consulta atravessa serialização, o processo do engine e a deserialização. É um custo fixo por query, independente de ela ser trivial ou complexa.
Desde o Prisma 6, com driver adapters (@prisma/adapter-pg para PostgreSQL) e engineType = "client", o engine binário sai do caminho: o Prisma passa a gerar o SQL no próprio processo Node.js e a usar o driver pg diretamente. Este blog roda exatamente nessa configuração em produção, com @prisma/adapter-pg sobre um pg.Pool, e a documentação oficial descreve os limites atuais dos adapters.
import { PrismaClient } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new PrismaPg(pool);
const prisma = new PrismaClient({ adapter });Como o Drizzle executa uma query
O Drizzle é um construtor de SQL tipado sobre o driver nativo (postgres, pg, node-postgres). A consulta que você escreve em TypeScript vira uma string SQL quase literal, sem processo intermediário. Não há engine, e o pacote é pequeno, o que pesa em ambientes com cold start (serverless e edge).
Relações: duas queries ou um JOIN
Quando você usa include no Prisma, ele executa consultas separadas e junta o resultado no JavaScript (o comportamento padrão, chamado de "join strategy: query"; versões recentes permitem relationJoins para gerar JOIN no banco, e vale conferir a documentação da sua versão). No Drizzle, leftJoin gera um único JOIN no SQL, e a query builder de relações (db.query) também resolve em uma ida ao banco na maioria dos casos.
A consequência prática: com relações, a diferença entre os dois não é "um é mais rápido"; é "quantas idas ao banco cada um faz para a mesma pergunta". Isso você enxerga com EXPLAIN e com o log de queries, sem cronômetro.
Bundle e cold start
O Prisma com engine binário carrega um executável; com adapter, carrega apenas JavaScript, mas o cliente gerado continua maior que o do Drizzle. Em funções serverless, meça o tempo de inicialização com o adapter ligado antes de concluir qualquer coisa: o cenário mudou muito entre o Prisma 5 e o 6.
Roteiro de benchmark que você roda
O roteiro abaixo é o mesmo que a versão anterior deste artigo descrevia, agora apresentado como procedimento, e não como resultado. Registre versões (npx prisma --version, npm ls drizzle-orm postgres pg), hardware, versão do PostgreSQL e o seed antes de comparar qualquer coisa.
Ambiente
- PostgreSQL 16 local ou em contêiner, na mesma máquina dos scripts (latência de rede não entra na conta).
- Node.js 22 LTS.
- Dataset: 100.000 registros em
userse 500.000 emposts, gerados pelo seu seed e versionados junto com os scripts. - Ferramenta: hyperfine para os comandos e
performance.now()dentro dos scripts.
Projetos
mkdir bench-prisma && cd bench-prisma
npm init -y
npm install prisma @prisma/client @prisma/adapter-pg pg
npx prisma init --datasource-provider postgresqlmkdir bench-drizzle && cd bench-drizzle
npm init -y
npm install drizzle-orm postgres
npm install -D drizzle-kitSchema compartilhado
Prisma (schema.prisma):
generator client {
provider = "prisma-client-js"
engineType = "client"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
}npx prisma migrate dev --name init
npx prisma generateDrizzle (schema.ts):
import { pgTable, serial, text, boolean, integer, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
email: text("email").unique().notNull(),
name: text("name").notNull(),
createdAt: timestamp("created_at").defaultNow(),
});
export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
content: text("content").notNull(),
published: boolean("published").default(false),
authorId: integer("author_id").references(() => users.id).notNull(),
createdAt: timestamp("created_at").defaultNow(),
});npx drizzle-kit generate
npx drizzle-kit migrateOs cinco cenários
- Select simples: buscar um usuário por ID.
- Select com relação: buscar usuário com seus posts.
- Insert em lote: inserir 1.000 registros.
- Update condicional: atualizar posts de um autor.
- Agregação: contar posts publicados por usuário.
Cada cenário roda 1.000 iterações depois de 100 iterações de aquecimento descartadas, e a comparação usa a mediana de pelo menos 5 rodadas, não a média de uma.
Cenário 1: select simples
Prisma:
import { PrismaClient } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter: new PrismaPg(pool) });
async function benchSelectSimple() {
const start = performance.now();
for (let i = 0; i < 1000; i++) {
const id = Math.floor(Math.random() * 100000) + 1;
await prisma.user.findUnique({ where: { id } });
}
const elapsed = performance.now() - start;
console.log(`Prisma select simples: ${elapsed.toFixed(2)}ms total`);
console.log(`Mediana por query: calcule sobre as rodadas, não sobre a média`);
}
benchSelectSimple().finally(() => prisma.$disconnect());Drizzle:
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { eq } from "drizzle-orm";
import { users } from "./schema";
const client = postgres(process.env.DATABASE_URL!);
const db = drizzle(client);
async function benchSelectSimple() {
const start = performance.now();
for (let i = 0; i < 1000; i++) {
const id = Math.floor(Math.random() * 100000) + 1;
await db.select().from(users).where(eq(users.id, id)).limit(1);
}
const elapsed = performance.now() - start;
console.log(`Drizzle select simples: ${elapsed.toFixed(2)}ms total`);
}
benchSelectSimple().finally(() => client.end());Cenário 2: select com relação
Prisma:
await prisma.user.findUnique({
where: { id },
include: { posts: { take: 10 } },
});Drizzle:
import { users, posts } from "./schema";
import { eq } from "drizzle-orm";
const result = await db
.select()
.from(users)
.leftJoin(posts, eq(users.id, posts.authorId))
.where(eq(users.id, id))
.limit(10);Ligue o log de queries dos dois (log: ["query"] no Prisma, logger: true no Drizzle) e conte as idas ao banco antes de olhar o tempo.
Cenário 3: insert em lote
Prisma:
await prisma.post.createMany({
data: batchOf1000Posts,
skipDuplicates: true,
});Drizzle:
await db.insert(posts).values(batchOf1000Posts).onConflictDoNothing();Como registrar o resultado
Para cada cenário, guarde: versão das bibliotecas, EXPLAIN (ANALYZE, BUFFERS) da query gerada, quantidade de queries por operação, mediana e p95 das rodadas, e o RSS do processo antes e depois (process.memoryUsage().rss). Publique o repositório com o lockfile. Sem isso, é opinião.
Onde cada um se destaca
Developer experience
O Prisma Studio, a introspecção de banco e o prisma migrate são maduros. O Prisma Client gera tipos a partir do schema sem configuração extra. O Drizzle exige mais SQL do time, e em troca mostra exatamente o que vai para o banco.
Ecossistema
O Prisma tem documentação extensa e soluções para a maioria dos casos de borda. O Drizzle cresce rápido, mas ainda há lacunas de documentação e mudanças de API entre versões menores; leia o changelog antes de atualizar.
Produtos pagos
O Prisma oferece Accelerate (pooling global com cache) e Pulse (streams de mudança). São úteis se você quer o ecossistema, e são um fator de dependência se você não quer.
Quando usar cada um
Drizzle: APIs onde o custo por query importa, edge functions e serverless com cold start sensível, times confortáveis com SQL, controle total do SQL gerado.
Prisma: times vindos do frontend, prioridade em DX, migrações com histórico e ferramentas visuais, e projetos sem requisito extremo de latência. Com driver adapters, o argumento de performance contra o Prisma perdeu força; meça antes de decidir por ele.
Os dois juntos: Prisma para migrações e tipos, Drizzle (ou SQL puro) para as consultas críticas.
// Queries normais com Prisma
const user = await prisma.user.findUnique({ where: { id } });
// Query crítica com Drizzle
const analytics = await db
.select({
authorId: posts.authorId,
totalPosts: sql<number>`count(*)`,
avgLength: sql<number>`avg(length(${posts.content}))`,
})
.from(posts)
.where(eq(posts.published, true))
.groupBy(posts.authorId)
.orderBy(sql`count(*) desc`)
.limit(100);Typed SQL no Prisma
O Prisma permite escrever SQL puro com tipos gerados a partir do arquivo .sql:
import { prisma } from "./client";
import { getUserAnalytics } from "@prisma/client/sql";
const result = await prisma.$queryRawTyped(getUserAnalytics(userId));Isso reduz a vantagem do Drizzle nas consultas complexas, sem trocar de ORM.
Posição editorial
Este blog usa Prisma 6 com @prisma/adapter-pg em produção. A escolha veio da DX das migrações e do cliente tipado, e o adapter resolveu o que mais incomodava na configuração com engine. O Drizzle é a escolha natural para quem prioriza controle do SQL e tamanho de bundle; nada aqui diz que ele é "mais lento" ou "mais rápido" sem o seu número na tabela.
Na maioria dos projetos, o gargalo não é o ORM: é query N+1, índice ausente ou cache inexistente. Meça o que dói antes de trocar a biblioteca.
Leia também

Escrito por
Marcos Soares
Fullstack Developer · CEO da Agência Poti
Fullstack Developer e CEO da Agência Poti. Mais de 20 anos construindo arquiteturas cloud-native com React, Next.js e sistemas distribuídos. Parceiro comercial do estúdio iellou design. Fundador do Vivo de Código.
Comentários
Participe da discussão
Seja o primeiro a comentar!
Continue Aprofundando
Guias de integração relacionados
Conteúdo técnico toda semana
Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.
Sem spam. Cancele a qualquer momento com 1 clique.


