Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wdSYQ-00474O-0n for pgsql-bugs@arkaria.postgresql.org; Sat, 27 Jun 2026 12:54:38 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wdSYN-00FFVd-33 for pgsql-bugs@arkaria.postgresql.org; Sat, 27 Jun 2026 12:54:35 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wcPRU-001OOj-0Z for pgsql-bugs@lists.postgresql.org; Wed, 24 Jun 2026 15:23:08 +0000 Received: from mahout.postgresql.org ([2001:4800:3e1:1::227]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wcPRS-000000002lT-0jIU for pgsql-bugs@lists.postgresql.org; Wed, 24 Jun 2026 15:23:07 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=postgresql.org; s=20171124; h=Message-ID:Date:Reply-To:Cc:From:To:Subject: Content-Transfer-Encoding:MIME-Version:Content-Type:Sender:Content-ID: Content-Description:In-Reply-To:References; bh=63FC2g++RFeTgR+DULykg3VcXG5dtHsY67/1pQiR/pI=; b=M8JULKy6Q1Ae/Ni3r349WiCeR9 RsKxOSJ8oyKuW5s44NM2DodAPgDRYpL65xN0s2DVT90bpIQ3HQuHPrpS5I8pRNrLnc0I7v1eQro07 kCV56OV+4tBnUC/Gtd5LHHygwfuPQxQUCH/xiFssQO+ni6ETxRct3SFUbxeUh2m1aC5RV759zRxS4 2So7kCteApk3FLR91PPuK6lxqwuiRyRf/+GJnlkMJF6NV+53Wh3/SdggyHO8xyX6HOE179mG9kKC4 CM+SMuAn0JFuLWO3j5BN2FfmNHZUcYfwzYFNurguidrVSRcDHC9+DTeKV40N7Iun4msRo9kaGkDt3 Iy97S5xA==; Received: from wrigleys.postgresql.org ([2a02:16a8:dc51::60]) by mahout.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wcPRQ-005uvz-0K for pgsql-bugs@lists.postgresql.org; Wed, 24 Jun 2026 15:23:04 +0000 Received: from localhost ([127.0.0.1] helo=wrigleys.postgresql.org) by wrigleys.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wcPRO-000R25-19 for pgsql-bugs@lists.postgresql.org; Wed, 24 Jun 2026 15:23:03 +0000 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Subject: BUG #19536: UPDATE RETURNING OLD value is stale after concurrent update when table has a BEFORE UPDATE trigger To: pgsql-bugs@lists.postgresql.org From: PG Bug reporting form Cc: bobergj@gmail.com Reply-To: bobergj@gmail.com, pgsql-bugs@lists.postgresql.org Date: Wed, 24 Jun 2026 15:22:14 +0000 Message-ID: <19536-73ce5847e6c0e7b1@postgresql.org> X-Auto-Response-Suppress: All Auto-Submitted: auto-generated List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk The following bug has been logged on the website: Bug reference: 19536 Logged by: Jonas Boberg Email address: bobergj@gmail.com PostgreSQL version: 18.4 Operating system: Docker image postgres:18.4-alpine3.23 Description: =20 In an UPDATE ... RETURNING OLD under isolation level READ COMMITTED, when the table has a BEFORE UPDATE trigger, the OLD value is stale after a concurrent update. SELECT version(); > PostgreSQL 18.4 on aarch64-unknown-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit ## How to reproduce Setup: ------- CREATE TABLE t ( id int PRIMARY KEY, n int NOT NULL ); CREATE FUNCTION before_update_noop() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END; $$; CREATE TRIGGER t_before_update BEFORE UPDATE ON t FOR EACH ROW EXECUTE FUNCTION before_update_noop(); INSERT INTO t VALUES (1, 7); ----- psql session 1: ------ BEGIN ISOLATION LEVEL READ COMMITTED; UPDATE t SET n =3D n + 10 WHERE id =3D 1; -- do not commit, hold the row lock ------ Leave session 1 open psql session 2: ----- BEGIN ISOLATION LEVEL READ COMMITTED; UPDATE t SET n =3D n + 1 WHERE id =3D 1 RETURNING OLD.n AS old_n, NEW.n AS new_n, NEW.n - 1 AS expected_old; ---- Session 2 blocks. Now commit in session 1: ---- COMMIT; ---- ## Actual result Session 2 outputs: old_n | new_n | expected_old -------+-------+-------------- 7 | 18 | 17 (1 row) Here OLD.n is the pre-concurrent update value, while NEW.n is the value written by the concurrent update. ## Expected result old_n | new_n | expected_old -------+-------+-------------- 17 | 18 | 17 If I drop the trigger and repeat the test, session 2 returns the expected result.