diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index e49affba9ee102e01fc8029beab14077f9d8b566..28fccaf381edb6e64e951f86d1d4e58e7efa230f 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -89,9 +89,10 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	 * because this trigger gets queued only in response to index insertions;
 	 * which means it does not get queued for HOT updates.  The row we are
 	 * called for might now be dead, but have a live HOT child, in which case
-	 * we still need to make the check.  Therefore we have to use
-	 * heap_hot_search, not just HeapTupleSatisfiesVisibility as is done in
-	 * the comparable test in RI_FKey_check.
+	 * we still need to make the check --- effectively, we're applying the
+	 * check against the live child row, although we can use the values from
+	 * this row since by definition all columns of interest to us are the
+	 * same.
 	 *
 	 * This might look like just an optimization, because the index AM will
 	 * make this identical test before throwing an error.  But it's actually
@@ -159,7 +160,9 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	{
 		/*
 		 * Note: this is not a real insert; it is a check that the index entry
-		 * that has already been inserted is unique.
+		 * that has already been inserted is unique.  Passing t_self is
+		 * correct even if t_self is now dead, because that is the TID the
+		 * index will know about.
 		 */
 		index_insert(indexRel, values, isnull, &(new_row->t_self),
 					 trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
@@ -168,10 +171,12 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	{
 		/*
 		 * For exclusion constraints we just do the normal check, but now it's
-		 * okay to throw error.
+		 * okay to throw error.  In the HOT-update case, we must use the live
+		 * HOT child's TID here, else check_exclusion_constraint will think
+		 * the child is a conflict.
 		 */
 		check_exclusion_constraint(trigdata->tg_relation, indexRel, indexInfo,
-								   &(new_row->t_self), values, isnull,
+								   &tmptid, values, isnull,
 								   estate, false);
 	}
 
diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source
index 7647544f9b9336e11342573e6dc5d9c75196dd8e..dbab8f159b8436bae7ecb56ddf299997cbfdf758 100644
--- a/src/test/regress/input/constraints.source
+++ b/src/test/regress/input/constraints.source
@@ -467,6 +467,7 @@ DROP TABLE circles;
 
 CREATE TABLE deferred_excl (
   f1 int,
+  f2 int,
   CONSTRAINT deferred_excl_con EXCLUDE (f1 WITH =) INITIALLY DEFERRED
 );
 
@@ -482,6 +483,15 @@ INSERT INTO deferred_excl VALUES(3);
 INSERT INTO deferred_excl VALUES(3); -- no fail here
 COMMIT; -- should fail here
 
+-- bug #13148: deferred constraint versus HOT update
+BEGIN;
+INSERT INTO deferred_excl VALUES(2, 1); -- no fail here
+DELETE FROM deferred_excl WHERE f1 = 2 AND f2 IS NULL; -- remove old row
+UPDATE deferred_excl SET f2 = 2 WHERE f1 = 2;
+COMMIT; -- should not fail
+
+SELECT * FROM deferred_excl;
+
 ALTER TABLE deferred_excl DROP CONSTRAINT deferred_excl_con;
 
 -- This should fail, but worth testing because of HOT updates
diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source
index bbe4ed1976046fd1d7506e87ce12841ce5345291..1c4a044052793273c91cf4eb2281073542bb23e7 100644
--- a/src/test/regress/output/constraints.source
+++ b/src/test/regress/output/constraints.source
@@ -634,6 +634,7 @@ DROP TABLE circles;
 -- Check deferred exclusion constraint
 CREATE TABLE deferred_excl (
   f1 int,
+  f2 int,
   CONSTRAINT deferred_excl_con EXCLUDE (f1 WITH =) INITIALLY DEFERRED
 );
 INSERT INTO deferred_excl VALUES(1);
@@ -654,6 +655,19 @@ INSERT INTO deferred_excl VALUES(3); -- no fail here
 COMMIT; -- should fail here
 ERROR:  conflicting key value violates exclusion constraint "deferred_excl_con"
 DETAIL:  Key (f1)=(3) conflicts with existing key (f1)=(3).
+-- bug #13148: deferred constraint versus HOT update
+BEGIN;
+INSERT INTO deferred_excl VALUES(2, 1); -- no fail here
+DELETE FROM deferred_excl WHERE f1 = 2 AND f2 IS NULL; -- remove old row
+UPDATE deferred_excl SET f2 = 2 WHERE f1 = 2;
+COMMIT; -- should not fail
+SELECT * FROM deferred_excl;
+ f1 | f2 
+----+----
+  1 |   
+  2 |  2
+(2 rows)
+
 ALTER TABLE deferred_excl DROP CONSTRAINT deferred_excl_con;
 -- This should fail, but worth testing because of HOT updates
 UPDATE deferred_excl SET f1 = 3;