I don't think this has anything to do with how UPDATEs are handled by Postgres or MySQL (or Uber's somewhat moot reasons to switch back)
Additionally, with the given setup, Postgres will not create a new row version for every update because no index values are being changed. This case is known as a HOT (Heap-Only Tupple) update. See e.g. here, here or here (plus: as a reaction on Uber's critic an optimization for this is currently being worked on)
The culprit of a deadlock is always acquiring locks in different order from different threads - and that is independent on how the lock is acquired or how the change is physically done in the background.
The big difference between MySQL/InnoDB and Postgres is that MySQL always uses what is called an "index organized table" in Oracle (or a "clustered index" in SQL Server). So the rows of a table are actually stored in an index structure (which isn't always a good choice).
This in turn means that the rows are physically "sorted" by the primary key (if there is no primary key, MySQL will add an invisible column for that) and an update on a table without an index will simply scan the whole table until all rows matching the where clause are found.
The "sorted" storage imposed by an index organized table means the rows are (most probably) always scanned in the same order for each thread - which in turn means the locks are acquired in the same order for each thread - thus avoiding the deadlock.
Postgres does not have index organized tables (clustered indexes) and thus the order in which the locks are obtained is not determined by some implicit order and hence the deadlocks can occur.
Given the nature of regular tables in Oracle (or in SQL Server for tables without a clustered index) I wouldn't be surprised if Oracle or SQL Server showed the same behavior as Postgres.
I tried to reproduce this and created a little (Java) test program.
The table was created like this:
create table positions
(
id integer,
value_1 numeric(18,12),
value_2 numeric(18,12),
updated_at timestamp
);
insert into positions (id, value_1, value_2)
select i, random() * 50000 + 1, random() * 50000 + 1
from generate_series(1, 50000) i;
So I have 50.000 rows in the table with the value_1
and value_2
columns ranging from 1 to 50.000
The Java program created 50 threads, connected each one to the database and once all were connected the actual updates were started in parallel - this was done to avoid that the first thread was already finished before the last one connected).
update positions
set updated_at = current_timestamp
where value_1 between 5000 and 30000;
So it's updating roughly 25.000 rows (half of the table).
Neither in Postgres 9.5, nor in 9.6 did I get a deadlock. After lowering the deadlock timeout from 1 second to 25ms I still did not get a deadlock. Although I do get a lot of lock warnings like the following (if I enable log_lock_waits
)
2016-10-12 08:19:53 CEST postgres LOG: process 6252 still waiting for ExclusiveLock on tuple (9384,1) of relation 1828790 of database 12401 after 31.197 ms
2016-10-12 08:19:53 CEST postgres DETAIL: Process holding the lock: 11036. Wait queue: 1892, 6252, 9836.
Even after making every thread sleep for 25milliseconds after the update, but before the commit, I did not get any deadlocks.
I also increased the number of rows to 500.000 and the number of updated rows to 250.000, still no deadlock.
You can see the test program here: http://hastebin.com/usuruqolez.java