Live data from Hacker News

PostgreSQL internals: Things to know about update statements

patrick.engineering

41–50 of 61 posts

Re: PostgreSQL internals: Things to know about update statements

#41

One thing I'd really like PostgreSQL to add is LIMIT on update statement as this makes batching easier. E.g. UPDATE user_profile SET followers_count = 0 WHERE followers_count IS NULL LIMIT 10000 I don't care which rows gets updated, only that no more than 10000 rows get updated. After each UPDATE I will COMMIT and then repeat as long as the UPDATE returns a positive number of rows updated. This makes it much easier t…

This is one area where MySQL is ahead. UPDATE and DELETE can both use LIMIT.

Re: PostgreSQL internals: Things to know about update statements

#42
post #22

#2 is something that nearly no application I know handles, and it saddens me, but not to the extent that it seems to anger many of my fellows. It's perfectly okay to do so for YouTube, it's not worth bothering for many applications of less concurrency than that. Still, it fairly offends me that most applications exist in the middle, where they don't have read isolation, or use logical additions, or have a separate pr…

I'm convinced that this is due to developers over-relying on ORMs instead of delving into raw SQL. I'll give another, different but somewhat related example: consider a worker that works on a batch of rows, and wants to update each of the rows in the batch, each row with different data for that row. In raw SQL, this is simple enough with the UPDATE FROM VALUES pattern (see e.g. https://stackoverflow.com/a/18799497 ).…

I think maybe with Django, you could use a QuerySet with an F expression to do this? [0] Agreed that it's trivial in pure SQL. As a counter though, having worked at places using an ORM (mostly Django), and also where everything was raw SQL, the latter winds up having WILDLY inefficient schema and queries that the DB team (me) has to fix later.

I lost any respect for Prisma when I learned it doesn't do JOINs in the DB. [1]

[0]: https://docs.djangoproject.com/en/5.0/ref/models/expressions...

[1]: https://github.com/prisma/prisma/issues/5184

Re: PostgreSQL internals: Things to know about update statements

#43

Earlier quoted context omitted.

Can you really run updates on CTEs that are simple projections and selections? TIL.

No, you run a SELECT … LIMIT in a CTE, and the main query is an UPDATE … JOIN CTE ON mytable.id = CTE.id

But using a Common Table Expression is a workaround, kind of like using a paper towel as a plate. Just give us a plate...

Re: PostgreSQL internals: Things to know about update statements

#44

> This behavior may well be interpreted as a violation of the SQL standard. no it may not this is fully standard compatible behavior and even expected behavior for _any_ SQL database implementing a read committed transaction insulation level also it's a problem which on a theoretically level is not solvable with how SQL works, hence why on stricter isolation levels like serializable committing the transaction in such…

[flagged]

Re: PostgreSQL internals: Things to know about update statements

#46

Earlier quoted context omitted.

You can already achieve this with CTEs.

How?

Would you not just be able to do the CTE as you would a correlated sub query? Something like:

    WITH batch AS (
      SELECT id FROM user_profile
      WHERE followers_count IS NULL
      LIMIT 10000
    )
    UPDATE user_profile
    SET followers_count = 0
    FROM batch
    WHERE user_profile.id = batch.id
But with the difference that if you didn’t want to round-trip to the application for each batch you could now make this a recursive CTE?

Re: PostgreSQL internals: Things to know about update statements

#47
post #5

Earlier quoted context omitted.

This is transaction 101. You simply lock the row via a SELECT … FOR UPDATE when you read the value. Any application that actually cares about consistency of its data must be doing this. Or just do it one step so the update increments the value in place. That way you have an implicit lock.

Author here. Thank you (and others) for mentioning SELECT FOR UPDATE. Definitely missing at least a footnote in the article. Note that "one step" updates (e.g., SET followers_count = followers_count + 1) are still not in place. They are regular transactional updates that rewrite the entire row. Still, they can achieve higher throughput because there is no application/database roundtrip between locking and committing.

If it's not an indexed column (and your page isn't too full), it can do a HOT update, which is effectively the same as an in-place write if you're flushing to disk at page granularity anyway unless you have really wide rows.

Re: PostgreSQL internals: Things to know about update statements

#48
post #22

Earlier quoted context omitted.

I'm convinced that this is due to developers over-relying on ORMs instead of delving into raw SQL. I'll give another, different but somewhat related example: consider a worker that works on a batch of rows, and wants to update each of the rows in the batch, each row with different data for that row. In raw SQL, this is simple enough with the UPDATE FROM VALUES pattern (see e.g. https://stackoverflow.com/a/18799497 ).…

I think maybe with Django, you could use a QuerySet with an F expression to do this? [0] Agreed that it's trivial in pure SQL. As a counter though, having worked at places using an ORM (mostly Django), and also where everything was raw SQL, the latter winds up having WILDLY inefficient schema and queries that the DB team (me) has to fix later. I lost any respect for Prisma when I learned it doesn't do JOINs in the DB…

> raw SQL... winds up having WILDLY inefficient schema and queries that the DB team (me) has to fix later.

I won't dispute this, I'll just point out that (a) premature optimization is the root of all evil, (b) because it's raw SQL, it's easier to reason about and fix. Inefficient queries aren't really a problem in the early days when your whole dataset doesn't even add up to a gigabyte.

Re: PostgreSQL internals: Things to know about update statements

#49
post #3

In the Lost Updates section, a more straight forward solution is to use the FOR UPDATE clause in the first select statement. This locks the record and prevents concurrent updates.

When you're incrementing by using UPDATE ... SET value = value + 1, the database holds the locks for the minimum time needed. Everything else is less efficient. In more complex scenarios, FOR UPDATE is the solution.

When there's a big chance of multiple tasks grabbing the same rows, processing them, then updating them, marking them for update since the beginning is better. E.g. a message queue like structure where messages should be processed only once.

Re: PostgreSQL internals: Things to know about update statements

#50

> This behavior may well be interpreted as a violation of the SQL standard. no it may not this is fully standard compatible behavior and even expected behavior for _any_ SQL database implementing a read committed transaction insulation level also it's a problem which on a theoretically level is not solvable with how SQL works, hence why on stricter isolation levels like serializable committing the transaction in such…

Yeah, I thought the other two sections were good but the "lost updates" section was just wrong, and in my opinion bizarre. Bizarre because the author does talk about different isolation levels - what does he suppose the purpose of those different isolation levels to actually be if he thinks READ COMMITTED should behave the same as SERIALIZABLE?
Post reply on HN