diff --git a/changelogs/unreleased/gh-8852-vy-read-iterator-use-after-free-fix.md b/changelogs/unreleased/gh-8852-vy-read-iterator-use-after-free-fix.md
new file mode 100644
index 0000000000000000000000000000000000000000..3789b14284b2560a847ff684d36784507e4ae478
--- /dev/null
+++ b/changelogs/unreleased/gh-8852-vy-read-iterator-use-after-free-fix.md
@@ -0,0 +1,5 @@
+## bugfix/vinyl
+
+* Fixed a heap-use-after-free bug in the Vinyl read iterator caused by a race
+  between a disk read and a memory dump task. The bug could lead to a crash or
+  an invalid query result (gh-8852).
diff --git a/src/box/vy_history.c b/src/box/vy_history.c
index c3717b28fc2b6e26941d1564888a218c87851338..86ee362bf7365d4358f224c3f9643dda2a61063f 100644
--- a/src/box/vy_history.c
+++ b/src/box/vy_history.c
@@ -52,9 +52,15 @@ vy_history_append_stmt(struct vy_history *history, struct vy_entry entry)
 			 "struct vy_history_node");
 		return -1;
 	}
-	node->is_refable = vy_stmt_is_refable(entry.stmt);
-	if (node->is_refable)
+	if (vy_stmt_is_refable(entry.stmt)) {
 		tuple_ref(entry.stmt);
+	} else {
+		entry.stmt = vy_stmt_dup(entry.stmt);
+		if (entry.stmt == NULL) {
+			mempool_free(history->pool, node);
+			return -1;
+		}
+	}
 	node->entry = entry;
 	rlist_add_tail_entry(&history->stmts, node, link);
 	return 0;
@@ -65,8 +71,7 @@ vy_history_cleanup(struct vy_history *history)
 {
 	struct vy_history_node *node, *tmp;
 	rlist_foreach_entry_safe(node, &history->stmts, link, tmp) {
-		if (node->is_refable)
-			tuple_unref(node->entry.stmt);
+		tuple_unref(node->entry.stmt);
 		mempool_free(history->pool, node);
 	}
 	rlist_create(&history->stmts);
@@ -91,11 +96,6 @@ vy_history_apply(struct vy_history *history, struct key_def *cmp_def,
 			 * Ignore terminal delete unless the caller
 			 * explicitly asked to keep it.
 			 */
-		} else if (!node->is_refable) {
-			curr.hint = node->entry.hint;
-			curr.stmt = vy_stmt_dup(node->entry.stmt);
-			if (curr.stmt == NULL)
-				return -1;
 		} else {
 			curr = node->entry;
 			tuple_ref(curr.stmt);
diff --git a/src/box/vy_history.h b/src/box/vy_history.h
index b25c27f70f3bed08fea2c4b4c45b7596e4365270..08492a38b64282041ee47f271dfec3223175a2f7 100644
--- a/src/box/vy_history.h
+++ b/src/box/vy_history.h
@@ -59,22 +59,8 @@ struct vy_history {
 struct vy_history_node {
 	/** Link in a history list. */
 	struct rlist link;
-	/** History statement. Referenced if @is_refable is set. */
+	/** History statement. Always referenced. */
 	struct vy_entry entry;
-	/**
-	 * Set if the statement stored in this node is refable,
-	 * i.e. has a reference counter that can be incremented
-	 * to pin the statement in memory. Refable statements are
-	 * referenced by the history. It is a responsibility of
-	 * the user of the history to track lifetime of unrefable
-	 * statements.
-	 *
-	 * Note, we need to store this flag here, because by the
-	 * time we clean up a history list, unrefable statements
-	 * stored in it might have been deleted, thus making
-	 * vy_stmt_is_refable() unusable.
-	 */
-	bool is_refable;
 };
 
 /**
diff --git a/src/box/vy_read_iterator.c b/src/box/vy_read_iterator.c
index c5fb02a2751039e83d01156d0ab8ce2b15772ca9..570a57a7197962edf8530264eeb3c45793b83eeb 100644
--- a/src/box/vy_read_iterator.c
+++ b/src/box/vy_read_iterator.c
@@ -418,13 +418,24 @@ vy_read_iterator_restore_mem(struct vy_read_iterator *itr,
 	int cmp;
 	struct vy_read_src *src = &itr->src[itr->mem_src];
 
+	/*
+	 * 'next' may refer to a statement in the memory source history,
+	 * which may be cleaned up by vy_mem_iterator_restore(), so we need
+	 * to take a reference to it.
+	 */
+	struct tuple *next_stmt_ref = next->stmt;
+	if (next_stmt_ref != NULL)
+		tuple_ref(next_stmt_ref);
+
 	rc = vy_mem_iterator_restore(&src->mem_iterator,
 				     itr->last, &src->history);
 	if (rc < 0)
-		return -1; /* memory allocation error */
+		goto out; /* memory allocation error */
 	if (rc == 0)
-		return 0; /* nothing changed */
+		goto out; /* nothing changed */
 
+	/* The memory source was updated. Reevaluate it for 'next'. */
+	rc = 0;
 	struct vy_entry entry = vy_history_last_stmt(&src->history);
 	cmp = vy_read_iterator_cmp_stmt(itr, entry, *next);
 	if (cmp > 0) {
@@ -439,14 +450,15 @@ vy_read_iterator_restore_mem(struct vy_read_iterator *itr,
 		 */
 		if (src->front_id == itr->front_id)
 			vy_read_iterator_reevaluate_srcs(itr, next);
-		return 0;
+		goto out;
 	}
+	/* The new statement is a better candidate for 'next'. */
+	*next = entry;
 	if (cmp < 0) {
 		/*
 		 * The new statement precedes the current
 		 * candidate for the next key.
 		 */
-		*next = entry;
 		itr->front_id++;
 	} else {
 		/*
@@ -459,7 +471,10 @@ vy_read_iterator_restore_mem(struct vy_read_iterator *itr,
 			vy_history_cleanup(&cache_src->history);
 	}
 	src->front_id = itr->front_id;
-	return 0;
+out:
+	if (next_stmt_ref != NULL)
+		tuple_unref(next_stmt_ref);
+	return rc;
 }
 
 static void