TechFeedTechFeed
Backend

Prisma P2025, 레코드 없음, update | 로컬 행은 보이는데 프리뷰만 못 찾으면?

프리즈마 P2025는 표와 칸이 있어도 그 where가 가리키는 행이 없을 때 납니다. generate 초록과 프리뷰 User 빈 표는 다른 칸이고, 카카오 닉네임 update와 upsert, OrThrow를 먼저 봅니다. Prisma, update, 슈퍼베이스, 버셀, Next.js, 카카오 로그인, 1인 개발자 기준. 2026년 9월 프리즈마 오류·update 공식 문서.

by

프리즈마 P2025는 표와 칸이 있어도, 지금 붙은 디비에서 그 where가 가리키는 행이 없을 때 납니다. generate가 초록이어도 프리뷰 User 표가 비어 있으면 update는 그대로 이 코드를 던집니다.


로컬 스튜디오에는 내 카카오 아이디 행이 보이는데, 버셀 프리뷰에서 닉네임만 고치다 500이 뜨는 일, 1인으로 넥스트를 올리다 보면 한 번은 만납니다. 쿠키는 있는데 그 디비에는 가입 행이 없는 상태죠.


스키마를 다시 밀기 전에 codecause, where 값, 프리뷰 주소의 행 개수부터 보세요. 근거는 프리즈마 오류 참고 P2025update 문서, 5 버전 rejectOnNotFound 제거, 6 버전 NotFoundError 제거에 있습니다.


P2025는 표가 아니라 그 행이 없는 줄

P2025는 스키마가 아니라 그 행이 그 디비에 없다는 뜻입니다. 공식 문구는 An operation failed because it depends on one or more records that were required but not found이고, 뒤에 No record was found for an updateRecord to delete does not exist가 붙습니다. 표가 없으면 P2021, 칸이 없으면 P2022, 호스트까지 못 붙으면 P1001입니다. 여기는 표도 칸도 연결도 있고, 그 키의 행만 없는 줄이에요.


프리즈마 클라이언트 참고는 update가 대상 행이 없으면 PrismaClientKnownRequestError 코드 P2025를 던진다고 적습니다. 포스트그레스 UPDATE는 0건이어도 문장 자체는 성공으로 끝나는데, 프리즈마 update는 행 하나를 돌려줘야 해서 0건을 예외로 바꿉니다. 그래서 스튜디오에서 표가 보여도, 지금 쿼리의 where가 가리키는 값이 없으면 같은 빨간 줄이 납니다.


제가 쓰는 카카오 콜백도 로컬에선 닉네임이 바뀌고 프리뷰만 500이었습니다. 로그에 P2025와 meta.modelName: User, operation: an update가 찍혀 있었어요. 프리뷰 디비 User 표는 있었고, 행이 0개였습니다. 마이그레이션을 한 번 더 돌릴 자리가 아니었습니다.


먼저 기억할 것 | 표 없음은 P2021, 칸 없음은 P2022, 중복은 P2002, 연결 실패는 P1001입니다. P2025는 그 키가 가리키는 행이 지금 디비에 없을 때만 봅니다.


데이터베이스 표는 있는데 찾으려는 행만 비어 있는 서버실 통로
표와 칸이 있어도 where가 가리키는 행이 없으면 P2025가 난다

로컬에 행이 있는데 프리뷰만 못 찾는 네 칸

로컬 행과 프리뷰 행은 다른 디비입니다. 화면이 로그인된 것처럼 보여도, 프리뷰가 붙은 주소에 그 카카오 아이디가 있는지는 따로 세야 합니다. 네 칸으로 먼저 가릅니다.


증상먼저 할 일
다른 디비로컬 스튜디오엔 행이 있고 프리뷰만 P2025프리뷰 DATABASE_URL로 count, 로컬 id를 그대로 넣지 않기
가입이 안 됨쿠키는 있는데 User 행이 0개콜백이 create 없이 update만 타는지
키 타입카카오 id를 숫자로 넣고 칸은 문자열where 값과 스키마 타입을 로그로 나란히
관계 connect글 저장은 되고 작성자 연결만 P2025connect 대상 id가 그 디비에 있는지
정책에 가림서비스 롤은 되고 로그인 롤만 못 찾음RLS USING이 그 행을 숨기는지, 403과 코드를 구분

버셀 프리뷰 환경 변수는 로컬 환경 파일을 복사하지 않습니다. 넥스트 환경 변수 문서버셀 환경 변수 문서대로 프리뷰 칸에 연결 주소가 있어도, 그 주소가 로컬 도커가 아니라 슈퍼베이스 프로젝트인지만 보면 됩니다. 로컬에서 가입한 행은 프리뷰 표로 따라오지 않아요. 제가 스튜디오를 열었을 때도 로컬 표만 보고 있었습니다.


카카오 아이디 칸을 문자열로 두고 프로필 아이디를 숫자 그대로 넣으면, 타입은 통과해 보여도 값이 안 맞습니다. 문자열로 한 번 바꿔 로그에 찍고, 스튜디오에서 같은 값으로 검색해 보세요. 관계를 붙이는 연결은 대상 행이 없으면 P2025 메시지에 관계가 같이 나옵니다. 외래 키 제약 P2003과는 다른 줄입니다. 한국에서 맥으로 짜고 프리뷰만 리눅스 함수로 도는 구조라, 로컬 행을 증거로 삼으면 한 시간을 허비합니다.


update와 updateMany는 0건일 때 갈린다

update는 0건이면 던지고, updateMany는 숫자만 돌려줍니다. 행 하나가 꼭 있어야 하는 닉네임 갱신은 update가 맞고, 없어도 되는 정리 작업은 updateMany가 맞습니다. 없음을 만들기로 바꾸려면 upsert입니다.


호출0건일 때쓸 자리
update / deleteP2025그 행이 꼭 있어야 하는 저장
updateMany / deleteManycount 0, 예외 없음없어도 되는 일괄 정리
upsert없으면 create가입과 갱신을 한 칸에
findUniquenull없으면 분기할 조회
findUniqueOrThrow / findFirstOrThrowP2025없으면 실패로 끝낼 조회

프리즈마 CRUD 문서의 업서트는 조건이 맞으면 고치고, 없으면 만듭니다. 카카오 콜백처럼 첫 로그인과 재로그인이 같은 함수를 타면, 갱신만 두면 프리뷰 빈 표에서 바로 P2025입니다. 빈 갱신 객체로 유니크만 막는 패턴은 P2002 글의 자리이고, 여기는 행이 없을 때 만들지에만 손을 댑니다. 없어도 되는 일괄 정리는 숫자를 받는 쪽이 조용해서, 콜백 저장과 섞으면 사용자는 닉네임이 바뀐 줄 압니다.


프리즈마 8은 같은 상황을 뮤테이션 행 없음 코드로 적습니다. 지금 검색에 찍히는 줄은 5·6 버전의 P2025가 많습니다. 버전을 올리기 전에 콘솔의 코드 칸부터 읽으면 됩니다. 포스트그레스는 UPDATE 문서대로 0건이어도 문장 성공이라, 디비 로그에 에러가 없어도 클라이언트는 예외를 던집니다.


카카오 닉네임 update가 P2025를 던지는 줄
import { Prisma } from '@prisma/client' // 프리뷰 User 표가 비어 있으면 여기서 P2025 await prisma.user.update({ where: { kakaoId: String(kakaoId) }, data: { nickname }, }) // 0건이 허용이면 예외가 아니라 count const result = await prisma.user.updateMany({ where: { kakaoId: String(kakaoId) }, data: { nickname }, }) // result.count === 0 이면 그 디비에 행이 없음

카카오 콜백 500 뒤에 숨은 닉네임 갱신

카카오 콜백 500은 콘솔 주소가 아니라 User 행일 수 있습니다. 리다이렉트 URI가 맞고 인가 코드가 바뀌어도, 그다음에 닉네임을 update하면 프리뷰 빈 표에서 P2025가 납니다. 화면은 카카오까지 갔다가 우리 서버만 죽습니다.


카카오 REST 사용자 정보는 회원번호를 줍니다. 그 값을 유니크 키로 두고 첫 로그인에 생성을 빼면, 로컬에서 한 번 가입한 뒤에는 티가 안 납니다. 프리뷰 디비는 따로라서 같은 코드가 첫 요청부터 갱신만 탑니다. 제가 막힌 자리도 콜백 로그에는 카카오 회원번호가 있고, 프리즈마만 P2025였습니다. 인가 코드 교환은 됐는데 우리 표에 사람이 없었던 겁니다.


고치는 칸은 콘솔이 아니라 콜백 함수입니다. 없으면 만들고 있으면 닉네임만 고치는 업서트로 바꾸거나, 조회가 비면 생성으로 갈라야 합니다. 카카오 리다이렉트 오류는 콘솔 주소 칸이고, 여기는 행 칸입니다. 두 줄을 한 번에 고치지 마세요. 토큰은 받아 놓고 닉네임만 저장하다 500이면, 콘솔을 더 만져 봐야 행은 안 생깁니다.


로그인 콜백 뒤 사용자 행을 갱신하는 흐름을 떠올리게 하는 모니터와 책상
카카오 화면이 지나도 프리뷰 User 행이 없으면 닉네임 update에서 P2025가 난다
콜백에서 없으면 만들고 있으면 닉네임만 고치기
import { Prisma } from '@prisma/client' const kakaoId = String(kakaoIdFromRest) try { await prisma.user.upsert({ where: { kakaoId }, update: { nickname }, create: { kakaoId, nickname }, }) } catch (e) { if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2025') { // upsert가 아닌 update/delete/OrThrow에서 온 줄 console.error('행 없음', e.meta) return } throw e }

findUnique 다음 update는 레이스가 남는다

findUnique가 성공해도 그 사이 행이 사라질 수 있습니다. 조회가 null이 아니라고 바로 update하면, 그 사이에 탈퇴나 다른 요청의 delete가 끼면 P2025가 납니다. 가입 연타의 유니크 충돌은 P2002 자리이고, 여기는 있던 행이 없어진 자리입니다.


혼자 쓰는 관리 화면에서도 탭 두 개가 같은 글을 지우면 둘째가 P2025입니다. 그때 갱신을 감싸 코드가 P2025면 없는 글 응답으로 돌려주면 됩니다. 예외 처리 문서의 알려진 요청 오류가 그 칸입니다. 메시지를 잘라 읽지 말고 코드 칸을 보세요. 한글 메시지가 버전마다 조금 달라도 코드는 같습니다.


트랜잭션 안에서 읽고 고치면 그 사이의 삭제를 줄일 수 있습니다. 그래도 행이 없는 게 정상적인 응답이면, 예외를 서버 오류로 올리지 말고 빈 결과로 바꾸세요. 소프트 삭제로 행은 남기고 목록만 가리는 칸은 부분 유니크 글의 자리입니다. 하드 삭제한 뒤 같은 아이디로 갱신하면 여기 P2025입니다. 탈퇴 직후 같은 세션이 닉네임을 한 번 더 저장해도 같은 줄이 납니다.


함정 | findUnique가 객체를 줬다고 update가 된다고 믿지 마세요. 두 호출 사이에 행이 없어질 수 있고, 프리뷰와 로컬은 같은 객체가 아닙니다.


OrThrow와 NotFoundError는 같은 코드로 모인다

OrThrow와 update는 같은 P2025로 모입니다. 5 버전은 rejectOnNotFound를 빼고 findUniqueOrThrow / findFirstOrThrow로 옮겼습니다. 6 버전은 NotFoundError 클래스를 빼고, 그 자리도 PrismaClientKnownRequestError 코드 P2025입니다. 예전에 instanceof NotFoundError를 쓰던 분기는 지금 빌드가 깨지거나, 런타임에서 못 잡습니다.


조회를 실패로 끝낼 때만 OrThrow를 쓰세요. 없으면 가입 화면으로 보낼 자리는 findUniquenull을 주는 쪽이 맞습니다. 닉네임 저장을 OrThrow로 감싸 두면, 프리뷰 빈 표에서 조회 단계에서 이미 P2025가 나고, 콜백은 또 500입니다.


P2001은 where 조건으로 찾은 레코드가 없다는 다른 코드입니다. 검색에 둘 다 행 없음으로 나오지만, 지금 스택 트레이스가 update면 P2025를 먼저 보세요. 관계를 붙이다 대상이 없으면 메시지에 관계가 남고, 그래도 코드는 P2025인 경우가 많습니다.


조회와 갱신이 같은 실패 코드로 모이는 개념을 떠올리게 하는 서버 랙
findUniqueOrThrow와 update는 행이 없으면 둘 다 P2025다

고치기 전에 이 순서로 찍는다

코드와 cause, where 값, 프리뷰 행 개수를 이 순서로 찍습니다. 스키마를 밀거나 키를 돌리기 전에, 지금 붙은 주소에 그 행이 있는지만 보면 대부분 갈립니다.


  • [ ] 에러 code가 P2025인지 확인했다. P2021·P2022·P1001이면 다른 글
  • [ ] meta.modelName과 메시지 끝 cause를 읽었다
  • [ ] 로그의 where 값과 타입을 스키마 칸과 나란히 봤다
  • [ ] 프리뷰 DATABASE_URL로 그 모델 count를 찍어 0인지 봤다
  • [ ] 로컬 스튜디오 행의 id를 프리뷰에 그대로 넣지 않았다
  • [ ] 카카오 콜백이면 create 없이 update만 타는지 봤다

슈퍼베이스 프리즈마 안내는 연결 문자열과 풀러 쪽을 주로 다루고, 행 없음은 클라이언트 쿼리 칸입니다. 연결이 되면 P1001은 끝난 것이고, 그다음 빨간 줄이 P2025면 표를 다시 만들지 마세요. 권한 정책이 행을 숨기면 조회가 빈 값으로 보여 여기와 닮습니다. 넣기만 막히면 권한 글의 자리입니다. 시드 없이 올린 프리뷰는 표만 있고 사람은 없어서, 로컬에서 쓰던 내 계정 아이디를 그대로 넣으면 영원히 못 찾습니다.


프리뷰 주소에서 그 행이 있는지 먼저 세기
const kakaoId = String(process.argv[2] || '') const n = await prisma.user.count({ where: { kakaoId } }) const row = await prisma.user.findUnique({ where: { kakaoId } }) console.log('DATABASE_URL host', new URL(process.env.DATABASE_URL).host) console.log('count', n) console.log('row', row) // n === 0 이면 update를 고치기 전에 upsert/create 칸이다

자주 묻는 질문

로컬 스튜디오에 행이 보이면 P2025가 아닌가요?

로컬 표와 프리뷰 표는 다른 디비입니다. 스튜디오가 로컬 도커를 보고 있으면, 버셀 프리뷰의 슈퍼베이스 표는 비어 있을 수 있습니다. 에러가 난 프로세스의 DATABASE_URL로 count를 찍어야 합니다.


migrate를 다시 돌리면 고쳐지나요?

표가 없을 때(P2021)나 칸이 없을 때(P2022)의 자리입니다. P2025는 표가 있는 상태에서 그 키의 행만 없는 줄이라, 마이그레이션을 한 번 더 해도 행은 생기지 않습니다. 콜백의 create/upsert나 시드를 보세요.


updateMany로 바꾸면 되나요?

없어도 되는 정리 작업이면 count 0을 받으면 됩니다. 닉네임이 꼭 바뀌어야 하는 콜백은 예외를 숨길 뿐이라, 가입 행을 만드는 칸이 남습니다. 0건을 성공으로 보면 사용자는 저장됐다고 착각합니다.


카카오 콘솔 주소를 고쳤는데도 500입니다.

인가 코드와 리다이렉트 URI는 콘솔 칸입니다. 그다음 서버가 User를 update하면 행 칸입니다. 로그에 P2025가 있으면 콘솔을 더 만지지 말고, 프리뷰 User count와 upsert를 보세요.


findUniqueOrThrow를 쓰면 더 안전한가요?

없으면 실패로 끝낼 조회에만 맞습니다. 5 버전 이후 rejectOnNotFound는 빠졌고, 6 버전은 NotFoundError도 P2025로 모입니다. 없으면 가입으로 보낼 자리는 null을 받는 findUnique가 맞습니다.


RLS 403과 어떻게 나누나요?

넣기가 막히면 보통 403이나 권한 오류입니다. 정책이 행을 숨기면 클라이언트는 행이 없는 것처럼 보여 P2025가 날 수 있습니다. 서비스 롤로 count가 나오고 로그인 롤만 0이면 정책 칸을 보고, 두 롤 모두 0이면 진짜 빈 표입니다.


PrismaP2025update레코드upsert슈퍼베이스버셀PostgreSQL넥스트카카오백엔드개발자

함께 보면 좋은 문제 해결

EXPLORE / Backend

이어서 읽어보기

전체 토픽 둘러보기