diff --git a/src/box/read_view.c b/src/box/read_view.c
index c99a1ea34e00b1b4531a63559fccd95b77672138..819863224de0a56d5c38d5a08de42b522666a857 100644
--- a/src/box/read_view.c
+++ b/src/box/read_view.c
@@ -18,6 +18,7 @@
 #include "space.h"
 #include "space_cache.h"
 #include "trivia/util.h"
+#include "tuple.h"
 
 static bool
 default_space_filter(struct space *space, void *arg)
@@ -42,6 +43,7 @@ read_view_opts_create(struct read_view_opts *opts)
 	opts->filter_space = default_space_filter;
 	opts->filter_index = default_index_filter;
 	opts->filter_arg = NULL;
+	opts->needs_field_names = false;
 }
 
 static void
@@ -54,6 +56,7 @@ space_read_view_delete(struct space_read_view *space_rv)
 			index_read_view_delete(index_rv);
 		}
 	}
+	tuple_format_unref(space_rv->format);
 	TRASH(space_rv);
 	free(space_rv);
 }
@@ -61,6 +64,24 @@ space_read_view_delete(struct space_read_view *space_rv)
 static struct space_read_view *
 space_read_view_new(struct space *space, const struct read_view_opts *opts)
 {
+	struct tuple_format *format = tuple_format_runtime;
+	if (opts->needs_field_names) {
+		/**
+		 * Sic: Even though a tuple dictionary has a reference counter,
+		 * we can't reuse the tuple dictionary used by the space tuple
+		 * format, because it may change when the space is altered, see
+		 * tuple_dictionary_swap.
+		 */
+		struct tuple_dictionary *dict = tuple_dictionary_new(
+			space->def->fields, space->def->field_count);
+		if (dict == NULL)
+			return NULL;
+		format = runtime_tuple_format_new(dict);
+		tuple_dictionary_unref(dict);
+		if (format == NULL)
+			return NULL;
+	}
+
 	struct space_read_view *space_rv;
 	size_t index_map_size = sizeof(*space_rv->index_map) *
 				(space->index_id_max + 1);
@@ -76,6 +97,8 @@ space_read_view_new(struct space *space, const struct read_view_opts *opts)
 
 	space_rv->id = space_id(space);
 	space_rv->group_id = space_group_id(space);
+	space_rv->format = format;
+	tuple_format_ref(format);
 	space_rv->index_id_max = space->index_id_max;
 	memset(space_rv->index_map, 0, index_map_size);
 	for (uint32_t i = 0; i <= space->index_id_max; i++) {
diff --git a/src/box/read_view.h b/src/box/read_view.h
index d6aa859f5fb8897db3d7d46db50de970eea93e3a..ab7f37f54af88fbec176492477dd308301cd9431 100644
--- a/src/box/read_view.h
+++ b/src/box/read_view.h
@@ -27,6 +27,21 @@ struct space_read_view {
 	uint32_t id;
 	/** Space name. */
 	char *name;
+	/**
+	 * Runtime tuple format needed to access tuple field names by name.
+	 * Referenced (ref counter incremented).
+	 *
+	 * A new format is created only if read_view_opts::needs_field_names
+	 * is set, otherwise runtime_tuple_format is used.
+	 *
+	 * We can't just use the space tuple format as is because it allocates
+	 * tuples from the space engine arena, which is single-threaded, while
+	 * a read view may be used from threads other than tx. Good news is
+	 * runtime tuple formats are reusable so if we create more than one
+	 * read view of the same space, we will use just one tuple format for
+	 * them all.
+	 */
+	struct tuple_format *format;
 	/** Replication group id. See space_opts::group_id. */
 	uint32_t group_id;
 	/**
@@ -81,6 +96,13 @@ struct read_view_opts {
 	 * Argument passed to filter functions.
 	 */
 	void *filter_arg;
+	/**
+	 * If this flag is set, a new runtime tuple format will be created for
+	 * each read view space to support accessing tuple fields by name,
+	 * otherwise the preallocated name-less runtime tuple format will be
+	 * used instead.
+	 */
+	bool needs_field_names;
 };
 
 /** Sets read view options to default values. */