Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Row locking support in find*() queries (via FOR UPDATE) #17136

Open
thetminko opened this issue Jan 4, 2023 · 11 comments
Open

Row locking support in find*() queries (via FOR UPDATE) #17136

thetminko opened this issue Jan 4, 2023 · 11 comments
Labels
domain/client Issue in the "Client" domain: Prisma Client, Prisma Studio etc. kind/feature A request for a new feature. topic: database-functionality topic: findFirst() https://www.prisma.io/docs/orm/reference/prisma-client-reference#findfirst topic: findMany() topic: findUnique() topic: locking topic: transaction

Comments

@thetminko
Copy link

thetminko commented Jan 4, 2023

Problem

I just migrated from Sequelize and was quite a fan of Prisma for being type safe. Would Prisma be able to support SELECT FOR UPDATE row locking like Sequelize supports? Understand that Optimistic locking is preferred by Prisma but it would be great if we have the row locking mechanism too.
My problem: When multiple requests call this function concurrently, while first transaction is still processing, the subsequent transaction will see that the user is still active and do the processing again. If we could avoid it by locking the row, that would be great.

function deactivate() {
  const user = await prisma.$transaction(async tx => {
   const user = await tx.user.findUnique({ where: { id: user.id } });
   if (user.status !== 'ACTIVE') {
     throw new Error('User not active ...');
  }
  // some business processing (not long transaction)
  return tx.user.update({ where: { id: user.id } }, data: {  status: 'DELETED' });
 });

   await doAfterCommitProcessing(user);
}

Suggested solution

const user = await tx.user.findUnique({ where: { id: user.id }, lock: FOR_UPDATE });
So, the user record with specific id is locked till the transaction is completed.

Alternatives

Additional context

@jkomyno jkomyno added kind/feature A request for a new feature. domain/client Issue in the "Client" domain: Prisma Client, Prisma Studio etc. topic: transaction topic: prisma gaps labels Jan 5, 2023
@gomain
Copy link

gomain commented Jan 23, 2023

Yes, I too have a use case where I want racing reads to block.

@luxaritas
Copy link
Contributor

Is this a duplicate of #5983?

@janpio
Copy link
Member

janpio commented Mar 10, 2023

Yep, sounds similar but a bit ore specific maybe. Will clean up when we look at the issue in depth.

@mweel1
Copy link

mweel1 commented Mar 16, 2023

Same here.

  await prisma.$transaction(
      async (tx) => {
        let user = await tx.user.findFirst({
          where: {
            id: req.auth.userId,
          },
        });

        // update the user
        if (user) {
          const userWorkspace = await tx.userWorkspace.findFirstOrThrow({
            where: {
              userId: req.auth.userId,
              id: user.defaultWorkspaceId,
            },
          });

          workspaceId = userWorkspace.workspaceId;

          var updateUser = await tx.user.update({
            where: {
              id: req.auth.userId,
            },
            data: {
              fullName: req.body.fullName,
              email: req.body.primaryEmailAddress.emailAddress,
              auth_json: JSON.stringify(req.body),
            },
          });
        } else {
          workspaceId = "Personal";
          var userWorkspace = await tx.userWorkspace.create({
            data: {
              userId: req.auth.userId,
              label: "Personal",
              workspaceId: "Personal",
            },
          });

          let newUser = await tx.user.create({
            data: {
              id: req.auth.userId,
              fullName: req.body.fullName,
              email: req.body.primaryEmailAddress.emailAddress,
              auth_json: JSON.stringify(req.body),
              defaultWorkspaceId: userWorkspace.id,
            },
          });
        }
      },
      {
        isolationLevel: Prisma.Prisma.TransactionIsolationLevel.Serializable,
      }

Getting this error when two threads are inserting the same user, the read should to see if the user exists if another transaction is working on it.

image

@gomain
Copy link

gomain commented Mar 16, 2023

The current work-around is to either use $rawQuery to place FOR UPDATE. Or artificially place a mutex field (typically a boolean) and select via updating the field to true (lock it) where it is false (not locked).

This is important if the activities before updating the row involves other side effects that we definitely don't want to happen if the transaction would fail.

@KarolScibior
Copy link

Any news / plans regarding this feature?

@AlejandroFrias
Copy link

Another friendly ping. I have that exact user create error. But also am trying to avoid accruing interest on a loan multiple times in the same day on a background job that might retry randomly.

@janpio janpio changed the title Row locking support in Find queries Row locking support in find* queries Jun 22, 2023
@janpio janpio added topic: findFirst() https://www.prisma.io/docs/orm/reference/prisma-client-reference#findfirst topic: findUnique() topic: findMany() topic: locking topic: database-functionality and removed topic: prisma gaps labels Jun 22, 2023
@janpio janpio changed the title Row locking support in find* queries Row locking support in find* queries (via FOR UPDATE) Jun 22, 2023
@esmaeilzadeh
Copy link

Is there any plan to add "lock for update" feature to Prisma transactions?

@devSajan
Copy link

yes, I have the same requirement, when deleting a record another request can access it which should not happen.

@janpio janpio changed the title Row locking support in find* queries (via FOR UPDATE) Row locking support in find*() queries (via FOR UPDATE) Feb 21, 2024
@janpio
Copy link
Member

janpio commented Mar 1, 2024

Potentially related:
#8580
#5983

@easdkr
Copy link

easdkr commented Apr 2, 2024

Is there any plan to add "lock" (shared, exclusive, update... etc lock) to findXXX method?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain/client Issue in the "Client" domain: Prisma Client, Prisma Studio etc. kind/feature A request for a new feature. topic: database-functionality topic: findFirst() https://www.prisma.io/docs/orm/reference/prisma-client-reference#findfirst topic: findMany() topic: findUnique() topic: locking topic: transaction
Projects
None yet
Development

No branches or pull requests