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
API for interactive transactions with dependencies between write-operations #1844
Comments
👍 This seems like a very useful (if not essential) feature to me and has been the only real pain point for me so far as a new Prisma user. As you've identified, there are situations where you need to perform several mutations in sequence, making use of the response data as you go, but with the confidence that it'll be a single transaction that rolls back in the case of a failure. In my opinion, neither nested mutations or transactions for independent mutations seem quite sufficient for these use-cases. If it helps to have example use-cases for discussion, there's a little more detail about my specific requirement here: https://www.prisma.io/forum/t/transactions-when-the-business-logic-requires-more-than-just-a-nested-insert/5904 |
Yes, raw sql queries are possible see: https://www.prisma.io/docs/prisma-graphql-api/reference/raw-database-access-qwe4/ |
@pantharshit00 Thanks! Just saw that too :D |
Hi guys, any updates on this? :( |
We are working on a new client which will have this. Please check here: https://github.com/prisma/rfcs/blob/new-ts-client-rfc/text/0000-new-ts-client.md |
@pantharshit00 Nice :) Gonna take a look. For now, I'm using stored procedures and Prisma's |
This comment has been minimized.
This comment has been minimized.
hi any update on this? |
Will this be part of Prisma 2? |
Looking into ORM's for Node, Prism looks REALLY good (website, documentation, activity on Github, etc). But lacking/missing (manual) edit: Oops, I said migrations in place of transaction 🤦♂️ |
Quoting "Julien Goux" from this public Slack conversation:
const userExists = await db.users.find(newUser.id)
if (!userExists) {
await db.users.insert(newUser)
}
|
I want to respond to the use case from the comment above (with points made by @sorenbs), how to handle situations like this: const userExists = await db.users.find(newUser.id)
if (!userExists) {
await db.users.insert(newUser)
} If the user was created in the 15 ms that separates the two statements, then the insert statement will fail as the id is unique. So there is no need for a transaction in this case. Further, if the id isn’t unique, then using a transaction wouldn’t solve the problem. Transactions do not block the entire database, so unrelated queries are allowed to proceed concurrently. In this case, both of the inserts would be allowed to proceed because they are unrelated, even if a transaction was used. |
could you describe your use case in detail? |
Hello, can we expect this to land any time soon? Beta-9 for example? Thanks for info a your work 🙏 |
Doesn't repeatable read / serializable isolation levels help in such cases? |
Put it this way: without the ability to intermix database activity and application logic within the transaction scope, you risk the outcome becoming incoherent (ie: nonsense) data. The point of the transaction scope is to allow the database to help ensure that what you think is happening actually ends up being correctly represented in the database. It's really important that Prisma be able to do this. It's well and good to be able to have a hierarchy of related data be added as part of the same transaction (as is supported now) via the 'create()'. And, being able to batch up a number of simple requests as a set, within the same transaction (eg: If you can not be sure that data you read at point in time A has not changed by the time you commit the changes, there is a very good probability that eventually, the data you have will be garbage because it is no longer consistent like it should be. Instead, there will be garbage data. Data that's there, but, how it relates to other data is not able to be established. Having garbage in the database is something we generally want to avoid. I suppose, it could be said, that this is kind of where the database having a procedural language available sprang from. eg: stored procedures with transactional scope. By allowing us to intermix database activity and application logic in the client application, we avoid having to resort to using stored procedures to achieve the desired result. |
@pbelbin I personally find that having long running transactions encourages the behaviour you just described as bad. People fetch the data at the beginning, do some long operation with and external system and just save the whole object (overriding the data of other concurrent requests). The |
@Sytten , I guess it depends on what you think a 'long-running-transaction' is. I agree that having a transaction that is open for hours at a time is not desirable. The longer the transaction is open, the higher the chances are that the transaction will have to be abandoned anyway, because someone else changed something. My concept of a 'long-running-transaction' is much shorter than that. Even if it's just a few seconds, we still need a way to guarantee that data read at point in time A is still valid and consistent by the time the end of the transaction happens. We need the ability to read, do some logic, write/update/delete, (repeat as needed) then commit. |
A use case for myself is that I use Cockroach DB so being able to set the |
@
Dear Matthew I'm looking for documents that state how to write code transactionless, do you have some links/references that state the desavantages of LRT, and how to rewrite code bases ? Best regards, |
Heads-up: We have internal prototypes running and we're aiming for a preview release in 2.29, if no major blockers are uncovered along the way. |
This is great news! How do you plan to handle isolation levels? Will there be some option to set it depending on our use-case? Thanks! |
Thanks for pointing that out! We discussed this internally and landed on using "Interactive transactions" going forward instead. Client-side transactions felt like it could be wrongly perceived by developers as potentially be executed on the client (ie browser) side which is obviously not the case. We'll update the materials associated with the feature in various places. |
We're aware of additional requirements like this in the future. The first version will not expose isolation levels, but we designed transactions in a way that we can add it on top when it's clear what we want to do here. |
I've always thought that if you have to explain terminology, it's probably not ideal. Sounds like I maybe the only one that doesn't know about "interactive transactions", then in that case... named well. If not though, I'd still rather see them called transactions. No one needs to explain to me what a long running transaction is so my thinking is just call them "Transactions" and "Long Running Transactions". IMHO 🙂. |
Interactive Transactions are in Preview 🚀 You can enable them by upgrading to the latest Prisma ( generator client {
provider = "prisma-client-js"
previewFeatures = ["interactiveTransactions"]
} Here's a basic example that shows how you can add custom logic in between two writes within a single transaction: const video = await prisma.$transaction(async (prisma) => {
const video = await prisma.video.create({
data: {
shortcode: "",
url: "video.mp4",
},
})
const shortcode = computeShortCode(video.id)
return prisma.video.update({
data: { shortcode },
where: { id: video.id },
})
}) You can learn more in the documentation. You can share your feedback in this issue. We recommend you use interactive transactions as a last resort. The Prisma Client's nested write and atomic operations cover most of the use cases for transactions. Interactive transactions will fill in the gaps. When you do need to reach for interactive transactions, please use them with caution. Keeping transactions open for a long time hurts database performance and can even cause deadlocks. Try to avoid performing unbound network requests and executing slow queries inside your transaction functions. As a rule of thumb: get in and out as quick as possible! Note that we're tracking the following as separate feature requests: |
why am i getting this error with the samle code?
|
You are providing promise where array of promises is expected. |
Guys, i'm getting this error:
Here is my code:
Someone can help me? |
@Filip3Dev Please open a new issue and fill the bug issue template. Thanks. |
I'm getting this even with version 3.6.0 and |
@scefali Please open a new issue and fill the bug issue template. Thanks. |
I'd love to have that approach. It'll allow us to use transactions for tests: describe('some test', () => {
let trx: Transaction;
beforeEach(async () => {
trx = await prisma.$begin();
jest.mock('./prisma', () => trx);
});
afterEach(async () => {
await trx.$rollback();
});
it('.....', () => {
// ....
});
}); |
did you found any proper solution for this situation, Separating transactions from business logic. Or a good enough compromised solution? Thanks regardless |
As soon as the prisma allows the possibility of getting the transaction reference, that would be great! Just like @leandrofinger exemplified. On my case, i can't open a database transaction on my API endpoint without coupling my interface and prisma implementations. On Knex or TypeORM was very simple. Just like this snippet: https://github.com/rmanguinho/advanced-node/blob/aa69e98291b9f63c4ea0f7c19a551818b4208a7e/src/infra/repos/postgres/helpers/connection.ts |
the default timeout of 2000 ms is quite low. I suspect many people who are reporting issues like this: #13713 might actually be running into timeouts. |
for now, I've worked around this using this snippet: // helper, because the default prisma transaction timeouts are too small
export const prismaTransaction = <R>(fn: (prisma: Prisma.TransactionClient) => Promise<R>) => {
return prismaClient.$transaction(fn, {
timeout: 30000,
maxWait: 60000
})
} but we need a way to set these timeouts when we instantiate a prisma client |
Hey @capaj, I think it's a nice idea would you mind opening a feature request? |
This syntax allows for easier testing and more freedom with business logic. |
There already is a GitHub issue asking for a way to submit multiple mutations within the same HTTP request where all mutations are executed as one transaction.
However, this feature does not allow for creating a transaction where the mutations depend on each other. Here is an example where the second operation depends on the first:
This is currently not possible with the Prisma API as it would require having a long-running connection where the results of the operations are sent back and forth between Prisma and the database.
It should be considered whether it is helpful to add such an API to Prisma, or whether abstractions like nested mutations and the requested transactional execution of multiple independent transactions are the better approach for Prisma.
The text was updated successfully, but these errors were encountered: