diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index d0a2be926682fcdef65df789257e0fd316c657b4..e05f6ea6880b4aa0cf71aa580af07476e703e5f4 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -2644,7 +2644,7 @@ vy_index_commit_stmt(struct vy_index *index, struct vy_mem *mem,
 		vy_index_commit_upsert(index, mem, stmt);
 
 	/* Invalidate cache element. */
-	vy_cache_on_write(&index->cache, stmt);
+	vy_cache_on_write(&index->cache, stmt, NULL);
 }
 
 /*
@@ -2660,7 +2660,7 @@ vy_index_rollback_stmt(struct vy_index *index, struct vy_mem *mem,
 	vy_mem_rollback_stmt(mem, stmt);
 
 	/* Invalidate cache element. */
-	vy_cache_on_write(&index->cache, stmt);
+	vy_cache_on_write(&index->cache, stmt, NULL);
 }
 
 /**
@@ -2848,13 +2848,40 @@ vy_tx_write(struct vy_index *index, struct vy_mem *mem,
 	assert(*region_stmt == NULL ||
 	       vy_stmt_is_region_allocated(*region_stmt));
 
-	int rc = vy_index_set(index, mem, stmt, region_stmt);
-
 	/*
-	 * Invalidate cache element.
-	 */
-	vy_cache_on_write(&index->cache, stmt);
-	return rc;
+	 * The UPSERT statement can be applied to the cached
+	 * statement, because the cache always contains only
+	 * newest REPLACE statements. In such a case the UPSERT,
+	 * applied to the cached statement, can be inserted
+	 * instead of the original UPSERT.
+	 */
+	if (vy_stmt_type(stmt) == IPROTO_UPSERT) {
+		struct tuple *deleted = NULL;
+		/* Invalidate cache element. */
+		vy_cache_on_write(&index->cache, stmt, &deleted);
+		if (deleted != NULL) {
+			struct tuple *applied =
+				vy_apply_upsert(stmt, deleted, mem->key_def,
+						mem->format, mem->upsert_format,
+						false);
+			tuple_unref(deleted);
+			if (applied != NULL) {
+				assert(vy_stmt_type(applied) == IPROTO_REPLACE);
+				int rc = vy_index_set(index, mem, applied,
+						      region_stmt);
+				tuple_unref(applied);
+				return rc;
+			}
+			/*
+			 * Ignore a memory error, because it is
+			 * not critical to apply the optimization.
+			 */
+		}
+	} else {
+		/* Invalidate cache element. */
+		vy_cache_on_write(&index->cache, stmt, NULL);
+	}
+	return vy_index_set(index, mem, stmt, region_stmt);
 }
 
 /* {{{ Scheduler Task */
diff --git a/src/box/vy_cache.c b/src/box/vy_cache.c
index e31aa25c70b2fc96aeccfaf6d0241da95dc491ac..48f480ced76720e5de4a32113539963b47ddbae5 100644
--- a/src/box/vy_cache.c
+++ b/src/box/vy_cache.c
@@ -349,7 +349,8 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
 }
 
 void
-vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt)
+vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt,
+		  struct tuple **deleted)
 {
 	vy_cache_gc(cache->env);
 	bool exact = false;
@@ -409,6 +410,11 @@ vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt)
 	if (exact) {
 		cache->version++;
 		struct vy_cache_entry *to_delete = *entry;
+		assert(vy_stmt_type(to_delete->stmt) == IPROTO_REPLACE);
+		if (deleted != NULL) {
+			*deleted = to_delete->stmt;
+			tuple_ref(to_delete->stmt);
+		}
 		vy_cache_tree_delete(&cache->cache_tree, to_delete);
 		vy_cache_entry_delete(cache->env, to_delete);
 	}
diff --git a/src/box/vy_cache.h b/src/box/vy_cache.h
index f472b14e01922fbc14ac40dd6ef21ae1a231ecbf..0e7792b554bd0f201dbb6e49f0b7a3456d09fbef 100644
--- a/src/box/vy_cache.h
+++ b/src/box/vy_cache.h
@@ -190,9 +190,12 @@ vy_cache_add(struct vy_cache *cache, struct tuple *stmt,
  * Invalidate possibly cached value due to its overwriting
  * @param cache - pointer to tuple cache.
  * @param stmt - overwritten statement.
+ * @param[out] deleted - If not NULL, then is set to deleted
+ *             statement.
  */
 void
-vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt);
+vy_cache_on_write(struct vy_cache *cache, const struct tuple *stmt,
+		  struct tuple **deleted);
 
 
 /**
diff --git a/test/vinyl/upsert.result b/test/vinyl/upsert.result
index 9f48d6136ae3d50926d0a27902015fcd976e83c2..0d11b71db4fb652d4ec7c68d93891c47fc8602ea 100644
--- a/test/vinyl/upsert.result
+++ b/test/vinyl/upsert.result
@@ -739,7 +739,7 @@ new_stat = box.info.vinyl().performance["iterator"].run.lookup_count
 ...
 new_stat - old_stat
 ---
-- 2
+- 1
 ...
 old_stat = new_stat
 ---