diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 359aafea681..4c9a17a2fc9 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1644,6 +1644,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
 	while (es)
 	{
 		bool		completed;
+		bool		need_snapshot = !fcache->func->readonly_func && !XactReadOnly;
 
 		if (es->status == F_EXEC_START)
 		{
@@ -1653,7 +1654,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
 			 * visible.  Take a new snapshot if we don't have one yet,
 			 * otherwise just bump the command ID in the existing snapshot.
 			 */
-			if (!fcache->func->readonly_func)
+			if (need_snapshot)
 			{
 				CommandCounterIncrement();
 				if (!pushed_snapshot)
@@ -1667,7 +1668,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
 
 			postquel_start(es, fcache);
 		}
-		else if (!fcache->func->readonly_func && !pushed_snapshot)
+		else if (need_snapshot && !pushed_snapshot)
 		{
 			/* Re-establish active snapshot when re-entering function */
 			PushActiveSnapshot(es->qd->snapshot);
@@ -1946,13 +1947,15 @@ ShutdownSQLFunction(Datum arg)
 		/* Shut down anything still running */
 		if (es->status == F_EXEC_RUN)
 		{
+			bool		need_snapshot = !fcache->func->readonly_func && !XactReadOnly;
+
 			/* Re-establish active snapshot for any called functions */
-			if (!fcache->func->readonly_func)
+			if (need_snapshot)
 				PushActiveSnapshot(es->qd->snapshot);
 
 			postquel_end(es, fcache);
 
-			if (!fcache->func->readonly_func)
+			if (need_snapshot)
 				PopActiveSnapshot();
 		}
 		es = es->next;
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 11139a910b8..6985799d90f 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -2249,6 +2249,7 @@ ReorderBufferProcessTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
 			BeginInternalSubTransaction(streaming ? "stream" : "replay");
 		else
 			StartTransactionCommand();
+		XactReadOnly = true;
 
 		/*
 		 * We only need to send begin/begin-prepare for non-streamed
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b9acc790dc6..b27867d0613 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4013,7 +4013,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 	estate->retistuple = func->fn_retistuple;
 	estate->retisset = func->fn_retset;
 
-	estate->readonly_func = func->fn_readonly;
+	estate->readonly_func = func->fn_readonly || XactReadOnly;
 	estate->atomic = true;
 
 	estate->exitlabel = NULL;
