From 552f7bddd65f8d4047646ad9b891cdfd7c5edf60 Mon Sep 17 00:00:00 2001
From: z00347042 <zhuqi1@huawei.com>
Date: Thu, 11 Jun 2020 15:51:20 +0800
Subject: [PATCH] =?UTF-8?q?ARM=20TRX=5FSYS=20Hash=20Table=E5=8E=BB?=
 =?UTF-8?q?=E9=94=81=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt                                     |   1 +
 configure.cmake                                    |   1 -
 include/lf.h                                       |   7 +
 mysql-test/include/have_lock_free_readview.inc     |   6 +
 mysql-test/include/no_lock_free_readview.inc       |   6 +
 ...innodb_i_s_innodb_trx_lock_free_readview.result |  81 ++
 .../suite/innodb/t/innodb_i_s_innodb_trx.test      |   2 +
 .../innodb_i_s_innodb_trx_lock_free_readview.test  | 103 +++
 mysys/lf_hash.cc                                   | 161 ++++
 storage/innobase/api/api0api.cc                    |   8 +
 storage/innobase/buf/buf0buf.cc                    |  21 +
 storage/innobase/clone/clone0copy.cc               |   8 +
 storage/innobase/clone/clone0repl.cc               |   2 +
 storage/innobase/dict/dict0dd.cc                   |   6 +
 storage/innobase/dict/dict0mem.cc                  |   5 +
 storage/innobase/handler/ha_innodb.cc              |  56 +-
 storage/innobase/handler/ha_innopart.cc            |   8 +
 storage/innobase/handler/handler0alter.cc          |  12 +
 storage/innobase/handler/p_s.cc                    | 142 ++++
 storage/innobase/include/clone0repl.h              |   4 +
 storage/innobase/include/read0read.h               |   3 +
 storage/innobase/include/read0types.h              |  92 +++
 storage/innobase/include/row0vers.h                |   6 +
 storage/innobase/include/srv0srv.h                 |   3 +
 storage/innobase/include/sync0sync.h               |   3 +
 storage/innobase/include/sync0types.h              |   6 +
 storage/innobase/include/trx0roll.h                |  10 +
 storage/innobase/include/trx0sys.h                 | 518 +++++++++++++
 storage/innobase/include/trx0sys.ic                |   2 +
 storage/innobase/include/trx0trx.h                 | 199 ++++-
 storage/innobase/include/trx0trx.ic                |  20 +
 storage/innobase/include/trx0types.h               |   2 +
 storage/innobase/include/trx0undo.h                |   5 +
 storage/innobase/include/ut0counter.h              |   2 +-
 storage/innobase/include/ut0new.h                  |   2 +
 storage/innobase/lock/lock0lock.cc                 | 471 ++++++++++++
 storage/innobase/page/page0page.cc                 |   4 +
 storage/innobase/read/read0read.cc                 | 132 ++++
 storage/innobase/row/row0ins.cc                    |   4 +
 storage/innobase/row/row0merge.cc                  |  19 +
 storage/innobase/row/row0mysql.cc                  |   4 +
 storage/innobase/row/row0pread.cc                  |   7 +
 storage/innobase/row/row0row.cc                    |   7 +
 storage/innobase/row/row0sel.cc                    |  66 +-
 storage/innobase/row/row0upd.cc                    |   6 +
 storage/innobase/row/row0vers.cc                   |  61 +-
 storage/innobase/srv/srv0srv.cc                    |  17 +
 storage/innobase/srv/srv0start.cc                  |  36 +
 storage/innobase/sync/sync0debug.cc                |  14 +
 storage/innobase/sync/sync0sync.cc                 |   3 +
 storage/innobase/trx/trx0i_s.cc                    |  44 ++
 storage/innobase/trx/trx0purge.cc                  |  20 +
 storage/innobase/trx/trx0roll.cc                   | 104 +++
 storage/innobase/trx/trx0sys.cc                    | 581 ++++++++++++++-
 storage/innobase/trx/trx0trx.cc                    | 827 ++++++++++++++++++++-
 storage/innobase/trx/trx0undo.cc                   |  66 ++
 storage/innobase/ut/ut0new.cc                      |   4 +
 unittest/gunit/mysys_lf-t.cc                       |  17 +
 58 files changed, 4011 insertions(+), 16 deletions(-)
 create mode 100644 mysql-test/include/have_lock_free_readview.inc
 create mode 100644 mysql-test/include/no_lock_free_readview.inc
 create mode 100644 mysql-test/suite/innodb/r/innodb_i_s_innodb_trx_lock_free_readview.result
 create mode 100644 mysql-test/suite/innodb/t/innodb_i_s_innodb_trx_lock_free_readview.test

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ce1d1bb..b450a81 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1031,6 +1031,7 @@ ADD_DEFINITIONS(-D__STDC_LIMIT_MACROS)     # Enable C99 limit macros
 ADD_DEFINITIONS(-D__STDC_FORMAT_MACROS)    # Enable C99 printf format macros
 ADD_DEFINITIONS(-D_USE_MATH_DEFINES)       # Get access to M_PI, M_E, etc. in math.h
 ADD_DEFINITIONS(-DLZ4_DISABLE_DEPRECATE_WARNINGS) # C++14 deprecation warnings in LZ4.
+ADD_DEFINITIONS(-DHAVE_LOCK_FREE_READVIEW) # Enable lock free readview feature
 
 OPTION(ENABLE_EXPERIMENT_SYSVARS "Expose ussually hidden system variables to allow experiments" OFF)
 IF(ENABLE_EXPERIMENT_SYSVARS)
diff --git a/configure.cmake b/configure.cmake
index dc54abf..ec61824 100644
--- a/configure.cmake
+++ b/configure.cmake
@@ -31,7 +31,6 @@ INCLUDE (CheckCSourceRuns)
 INCLUDE (CheckCXXSourceRuns)
 INCLUDE (CheckSymbolExists)
 
-
 # System type affects version_compile_os variable 
 IF(NOT SYSTEM_TYPE)
   IF(PLATFORM)
diff --git a/include/lf.h b/include/lf.h
index 53898f4..6e25ec6 100644
--- a/include/lf.h
+++ b/include/lf.h
@@ -159,6 +159,9 @@ struct LF_HASH;
 
 typedef uint lf_hash_func(const LF_HASH *, const uchar *, size_t);
 typedef void lf_hash_init_func(uchar *dst, const uchar *src);
+#ifdef HAVE_LOCK_FREE_READVIEW
+typedef bool lf_hash_walk_func(void *, void *);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 #define LF_HASH_UNIQUE 1
 #define MY_LF_ERRPTR ((void *)(intptr)1)
@@ -214,6 +217,10 @@ int lf_hash_insert(LF_HASH *hash, LF_PINS *pins, const void *data);
 void *lf_hash_search(LF_HASH *hash, LF_PINS *pins, const void *key,
                      uint keylen);
 int lf_hash_delete(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen);
+#ifdef HAVE_LOCK_FREE_READVIEW
+int lf_hash_iterate(LF_HASH *hash, LF_PINS *pins, const lf_hash_walk_func *callback,
+                    const void *argument);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 static inline LF_PINS *lf_hash_get_pins(LF_HASH *hash) {
   return lf_pinbox_get_pins(&hash->alloc.pinbox);
diff --git a/mysql-test/include/have_lock_free_readview.inc b/mysql-test/include/have_lock_free_readview.inc
new file mode 100644
index 0000000..a24932e
--- /dev/null
+++ b/mysql-test/include/have_lock_free_readview.inc
@@ -0,0 +1,6 @@
+--let $_have_lock_free_readview = `select VARIABLE_VALUE from performance_schema.global_status where VARIABLE_NAME = 'Innodb_have_lock_free_readview';`
+
+if ($_have_lock_free_readview == "")
+{
+  --skip Need lock free readview binaries
+}
diff --git a/mysql-test/include/no_lock_free_readview.inc b/mysql-test/include/no_lock_free_readview.inc
new file mode 100644
index 0000000..779d9eb
--- /dev/null
+++ b/mysql-test/include/no_lock_free_readview.inc
@@ -0,0 +1,6 @@
+--let $_have_lock_free_readview = `select VARIABLE_VALUE from performance_schema.global_status where VARIABLE_NAME = 'Innodb_have_lock_free_readview';`
+
+if ($_have_lock_free_readview == "ON")
+{
+  --skip Doesn't support lock free readview binaries
+}
diff --git a/mysql-test/suite/innodb/r/innodb_i_s_innodb_trx_lock_free_readview.result b/mysql-test/suite/innodb/r/innodb_i_s_innodb_trx_lock_free_readview.result
new file mode 100644
index 0000000..73e742c
--- /dev/null
+++ b/mysql-test/suite/innodb/r/innodb_i_s_innodb_trx_lock_free_readview.result
@@ -0,0 +1,81 @@
+SET GLOBAL innodb_lock_wait_timeout=600;
+DESCRIBE INFORMATION_SCHEMA.INNODB_TRX;
+Field	Type	Null	Key	Default	Extra
+trx_id	varchar(18)	NO			
+trx_state	varchar(13)	NO			
+trx_started	datetime	NO			
+trx_requested_lock_id	varchar(105)	YES			
+trx_wait_started	datetime	YES			
+trx_weight	bigint unsigned	NO			
+trx_mysql_thread_id	bigint unsigned	NO			
+trx_query	varchar(1024)	YES			
+trx_operation_state	varchar(64)	YES			
+trx_tables_in_use	bigint unsigned	NO			
+trx_tables_locked	bigint unsigned	NO			
+trx_lock_structs	bigint unsigned	NO			
+trx_lock_memory_bytes	bigint unsigned	NO			
+trx_rows_locked	bigint unsigned	NO			
+trx_rows_modified	bigint unsigned	NO			
+trx_concurrency_tickets	bigint unsigned	NO			
+trx_isolation_level	varchar(16)	NO			
+trx_unique_checks	int	NO			
+trx_foreign_key_checks	int	NO			
+trx_last_foreign_key_error	varchar(256)	YES			
+trx_adaptive_hash_latched	int	NO			
+trx_adaptive_hash_timeout	bigint unsigned	NO			
+trx_is_read_only	int	NO			
+trx_autocommit_non_locking	int	NO			
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+c01 INT,
+c02 INT,
+PRIMARY KEY (c01)
+) ENGINE=INNODB STATS_AUTO_RECALC=0;
+INSERT INTO t1 VALUES
+(1,2),(2,4),(3,6),(4,8);
+CREATE TABLE t2 (
+c01 INT,
+c02 INT,
+PRIMARY KEY (c01),
+CONSTRAINT fk1 FOREIGN KEY (c02) REFERENCES t1 (c01)
+) ENGINE=INNODB STATS_AUTO_RECALC=0;
+INSERT INTO t2 VALUES
+(1,1),(2,2),(3,3);
+SET autocommit=0;
+INSERT INTO t1 VALUES (5,10);
+SELECT * FROM t1 FOR UPDATE;
+c01	c02
+1	2
+2	4
+3	6
+4	8
+5	10
+SELECT trx_state, trx_weight, trx_tables_in_use, trx_tables_locked,
+trx_rows_locked, trx_rows_modified, trx_concurrency_tickets,
+trx_isolation_level, trx_unique_checks, trx_foreign_key_checks
+FROM INFORMATION_SCHEMA.INNODB_TRX;
+trx_state	trx_weight	trx_tables_in_use	trx_tables_locked	trx_rows_locked	trx_rows_modified	trx_concurrency_tickets	trx_isolation_level	trx_unique_checks	trx_foreign_key_checks
+RUNNING	3	0	1	5	1	0	REPEATABLE READ	1	1
+ROLLBACK;
+SET FOREIGN_KEY_CHECKS = 0;
+SET UNIQUE_CHECKS = 0;
+SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+BEGIN;
+INSERT INTO t1 VALUES (6,12);
+SELECT trx_isolation_level, trx_unique_checks, trx_foreign_key_checks
+FROM INFORMATION_SCHEMA.INNODB_TRX;
+trx_isolation_level	trx_unique_checks	trx_foreign_key_checks
+SERIALIZABLE	0	0
+ROLLBACK;
+SET FOREIGN_KEY_CHECKS = 1;
+SET UNIQUE_CHECKS = 1;
+BEGIN;
+INSERT INTO t2 VALUES (4,10);
+ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `fk1` FOREIGN KEY (`c02`) REFERENCES `t1` (`c01`))
+SELECT trx_state, trx_isolation_level, trx_last_foreign_key_error
+FROM INFORMATION_SCHEMA.INNODB_TRX;
+trx_state	trx_isolation_level	trx_last_foreign_key_error
+RUNNING	REPEATABLE READ	`test`.`t2`, CONSTRAINT `fk1` FOREIGN KEY (`c02`) REFERENCES `t1` (`c01`)
+DROP TABLE t2;
+DROP TABLE t1;
+SET GLOBAL innodb_lock_wait_timeout=default;
diff --git a/mysql-test/suite/innodb/t/innodb_i_s_innodb_trx.test b/mysql-test/suite/innodb/t/innodb_i_s_innodb_trx.test
index b92cc8f..1fbc71f 100644
--- a/mysql-test/suite/innodb/t/innodb_i_s_innodb_trx.test
+++ b/mysql-test/suite/innodb/t/innodb_i_s_innodb_trx.test
@@ -6,6 +6,8 @@
 
 # Make sure the locks keep waiting until they are released,
 # even on a busy system.
+--source include/no_lock_free_readview.inc
+
 SET GLOBAL innodb_lock_wait_timeout=600;
 
 DESCRIBE INFORMATION_SCHEMA.INNODB_TRX;
diff --git a/mysql-test/suite/innodb/t/innodb_i_s_innodb_trx_lock_free_readview.test b/mysql-test/suite/innodb/t/innodb_i_s_innodb_trx_lock_free_readview.test
new file mode 100644
index 0000000..7cc3709
--- /dev/null
+++ b/mysql-test/suite/innodb/t/innodb_i_s_innodb_trx_lock_free_readview.test
@@ -0,0 +1,103 @@
+
+#
+# Test that transaction data is correctly "visualized" in
+# INFORMATION_SCHEMA.INNODB_TRX
+#
+
+# Make sure the locks keep waiting until they are released,
+# even on a busy system.
+--source include/have_lock_free_readview.inc
+
+SET GLOBAL innodb_lock_wait_timeout=600;
+
+DESCRIBE INFORMATION_SCHEMA.INNODB_TRX;
+
+-- disable_warnings
+DROP TABLE IF EXISTS t1;
+-- enable_warnings
+
+CREATE TABLE t1 (
+	c01 INT,
+	c02 INT,
+	PRIMARY KEY (c01)
+) ENGINE=INNODB STATS_AUTO_RECALC=0;
+
+INSERT INTO t1 VALUES
+(1,2),(2,4),(3,6),(4,8);
+
+CREATE TABLE t2 (
+	c01 INT,
+	c02 INT,
+	PRIMARY KEY (c01),
+	CONSTRAINT fk1 FOREIGN KEY (c02) REFERENCES t1 (c01)
+) ENGINE=INNODB STATS_AUTO_RECALC=0;
+
+INSERT INTO t2 VALUES
+(1,1),(2,2),(3,3);
+
+-- source include/count_sessions.inc
+
+-- connect (con_trx,localhost,root,,)
+-- connect (con_verify_innodb_trx,localhost,root,,)
+
+-- connection con_trx
+SET autocommit=0;
+INSERT INTO t1 VALUES (5,10);
+SELECT * FROM t1 FOR UPDATE;
+
+let $wait_timeout= 300;
+let $wait_condition=
+  SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.INNODB_TRX;
+-- source include/wait_condition.inc
+
+-- connection con_verify_innodb_trx
+SELECT trx_state, trx_weight, trx_tables_in_use, trx_tables_locked,
+trx_rows_locked, trx_rows_modified, trx_concurrency_tickets,
+trx_isolation_level, trx_unique_checks, trx_foreign_key_checks
+FROM INFORMATION_SCHEMA.INNODB_TRX;
+
+-- connection con_trx
+ROLLBACK;
+SET FOREIGN_KEY_CHECKS = 0;
+SET UNIQUE_CHECKS = 0;
+SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
+BEGIN;
+INSERT INTO t1 VALUES (6,12);
+
+let $wait_timeout= 300;
+let $wait_condition=
+  SELECT trx_unique_checks = 0 FROM INFORMATION_SCHEMA.INNODB_TRX;
+-- source include/wait_condition.inc
+
+-- connection con_verify_innodb_trx
+SELECT trx_isolation_level, trx_unique_checks, trx_foreign_key_checks
+FROM INFORMATION_SCHEMA.INNODB_TRX;
+
+-- connection con_trx
+ROLLBACK;
+SET FOREIGN_KEY_CHECKS = 1;
+SET UNIQUE_CHECKS = 1;
+BEGIN;
+-- error 1452
+INSERT INTO t2 VALUES (4,10);
+
+let $wait_timeout= 300;
+let $wait_condition=
+  SELECT trx_unique_checks  = 1 FROM INFORMATION_SCHEMA.INNODB_TRX;
+-- source include/wait_condition.inc
+
+-- connection con_verify_innodb_trx
+SELECT trx_state, trx_isolation_level, trx_last_foreign_key_error
+FROM INFORMATION_SCHEMA.INNODB_TRX;
+
+-- connection default
+
+-- disconnect con_trx
+-- disconnect con_verify_innodb_trx
+
+DROP TABLE t2;
+DROP TABLE t1;
+
+-- source include/wait_until_count_sessions.inc
+
+SET GLOBAL innodb_lock_wait_timeout=default;
diff --git a/mysys/lf_hash.cc b/mysys/lf_hash.cc
index cf0cffa..e6f8ec7 100644
--- a/mysys/lf_hash.cc
+++ b/mysys/lf_hash.cc
@@ -101,6 +101,126 @@ static inline T *SET_DELETED(T *ptr) {
   return reinterpret_cast<T *>(i);
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/**
+  Walk the list, searching for an element or invoking a callback.
+
+  Search for hashnr/key/keylen in the list starting from 'head' and position the
+  cursor. The list is ORDER by hashnr, key
+
+  @param head         start walking the list from this node
+  @param cs           charset for comparing keys, nullptr if callback is used
+  @param hashnr       hash number to searching for
+  @param key          key to search for OR data for the callback
+  @param keylen       length of the key to compare, 0 if callback is used
+  @param cursor       for returning the found element
+  @param pins         see lf_alloc-pin.cc
+  @param callback     callback action, invoked for every element
+
+  @note
+    cursor is positioned in either case
+    pins[0..2] are used, they are not removed on return
+    callback might see some elements twice (because of retries)
+
+  @return
+    if find: 0 - not found
+             1 - found
+    if callback:
+             0 - ok
+             1 - error (callback returned true)
+*/
+static int my_lfind(std::atomic<LF_SLIST *> *head, CHARSET_INFO *cs,
+                    uint32 hashnr, const uchar *key, size_t keylen,
+                    CURSOR *cursor, LF_PINS *pins,
+                    lf_hash_walk_func *callback) {
+  uint32 cur_hashnr;
+  const uchar *cur_key;
+  size_t cur_keylen;
+  LF_SLIST *link;
+
+  /* should not be set both */
+  DBUG_ASSERT((cs == nullptr) || (callback == nullptr));
+  /* should not be set both */
+  DBUG_ASSERT((keylen == 0) || (callback == nullptr));
+
+retry:
+  cursor->prev = head;
+  do /* PTR() isn't necessary below, head is a dummy node */
+  {
+    cursor->curr = (LF_SLIST *)(*cursor->prev);
+    lf_pin(pins, 1, cursor->curr);
+  } while (*cursor->prev != cursor->curr && LF_BACKOFF);
+  for (;;) {
+
+    if (unlikely(!cursor->curr)) {
+      return 0; /* end of the list */
+    }
+
+    cur_hashnr = cursor->curr->hashnr;
+    cur_keylen = cursor->curr->keylen;
+    cur_key = cursor->curr->key;
+
+    do {
+      link = cursor->curr->link;
+      cursor->next = PTR(link);
+      lf_pin(pins, 0, cursor->next);
+    } while (link != cursor->curr->link && LF_BACKOFF);
+
+    if (!DELETED(link)) {
+      if (likely(callback != nullptr)) {
+        if ((cur_hashnr & 1) > 0 &&
+            callback(cursor->curr + 1,
+                     const_cast<void *>(static_cast<const void *>(key)))) {
+          return 1;
+        }
+      } else if (cur_hashnr >= hashnr) {
+        int r = 1;
+        if (cur_hashnr > hashnr ||
+            (r = my_strnncoll(cs, cur_key, cur_keylen, key, keylen)) >= 0) {
+          return !r;
+        }
+      }
+      cursor->prev = &(cursor->curr->link);
+      if (!(cur_hashnr & 1)) /* dummy node */
+        head = cursor->prev;
+      lf_pin(pins, 2, cursor->curr);
+    } else {
+      /*
+        we found a deleted node - be nice, help the other thread
+        and remove this deleted node
+      */
+      if (atomic_compare_exchange_strong(cursor->prev, &cursor->curr,
+                                         cursor->next)) {
+        lf_pinbox_free(pins, cursor->curr);
+      } else {
+        (void)LF_BACKOFF;
+        goto retry;
+      }
+    }
+    cursor->curr = cursor->next;
+    lf_pin(pins, 1, cursor->curr);
+  }
+}
+
+/*
+  DESCRIPTION
+    Search for hashnr/key/keylen in the list starting from 'head' and
+    position the cursor. The list is ORDER BY hashnr, key
+
+  RETURN
+    0 - not found
+    1 - found
+
+  NOTE
+    cursor is positioned in either case
+    pins[0..2] are used, they are NOT removed on return
+*/
+static int my_lfind(std::atomic<LF_SLIST *> *head, CHARSET_INFO *cs,
+                    uint32 hashnr, const uchar *key, size_t keylen,
+                    CURSOR *cursor, LF_PINS *pins) {
+  return my_lfind(head, cs, hashnr, key, keylen, cursor, pins, nullptr);
+}
+#else
 /*
   DESCRIPTION
     Search for hashnr/key/keylen in the list starting from 'head' and
@@ -173,6 +293,7 @@ retry:
     lf_pin(pins, 1, cursor->curr);
   }
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /**
   Search for list element satisfying condition specified by match
@@ -570,6 +691,46 @@ int lf_hash_delete(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen) {
   return 0;
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/**
+  Iterate over all elements in hash and call function with the element.
+
+  @note
+  If one of 'callback' invocations returns true the iteration aborts.
+  'action' might see some elements twice!
+
+  @return 0 if ok, or 1 if error (action returned true)
+*/
+int lf_hash_iterate(LF_HASH *hash, LF_PINS *pins, const lf_hash_walk_func *callback,
+                    const void *argument) {
+  CURSOR cursor;
+  uint bucket = 0;
+  int res;
+  std::atomic<LF_SLIST *> *el;
+
+  el = static_cast<std::atomic<LF_SLIST *> *>(
+      lf_dynarray_lvalue(&hash->array, bucket));
+  if (unlikely(el == nullptr)) {
+    /* if there's no bucket==0, the hash is empty */
+    return 0;
+  }
+
+  if (*el == nullptr && unlikely(initialize_bucket(hash, el, bucket, pins))) {
+    /* if there's no bucket==0, the hash is empty */
+    return 0;
+  }
+
+  res = my_lfind(el, nullptr, 0, static_cast<const uchar *>(argument), 0, &cursor,
+                 pins, callback);
+
+  lf_unpin(pins, 2);
+  lf_unpin(pins, 1);
+  lf_unpin(pins, 0);
+
+  return res;
+}
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
 /**
   Find hash element corresponding to the key.
 
diff --git a/storage/innobase/api/api0api.cc b/storage/innobase/api/api0api.cc
index 044ee0b..51c0a41 100644
--- a/storage/innobase/api/api0api.cc
+++ b/storage/innobase/api/api0api.cc
@@ -743,7 +743,11 @@ static ib_err_t ib_create_cursor(ib_crsr_t *ib_crsr,  /*!< out: InnoDB cursor */
       /* Assign a read view if the transaction does
       not have it yet */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+      prebuilt->trx->read_view.open(prebuilt->trx);
+#else
       trx_assign_read_view(prebuilt->trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
     }
 
     *ib_crsr = (ib_crsr_t)cursor;
@@ -934,7 +938,11 @@ ib_err_t ib_cursor_new_trx(ib_crsr_t ib_crsr, /*!< in/out: InnoDB cursor */
 
   cursor->valid_trx = TRUE;
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  prebuilt->trx->read_view.open(prebuilt->trx);
+#else
   trx_assign_read_view(prebuilt->trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   ib_qry_proc_free(&cursor->q_proc);
 
diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc
index 983603e..f3191db 100644
--- a/storage/innobase/buf/buf0buf.cc
+++ b/storage/innobase/buf/buf0buf.cc
@@ -2143,6 +2143,26 @@ withdraw_retry:
 
     lock_mutex_enter();
     trx_sys_mutex_enter();
+#ifdef HAVE_LOCK_FREE_READVIEW
+    bool found = false;
+    for (trx_t *trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx != nullptr;
+         trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+      if (trx->state != TRX_STATE_NOT_STARTED && trx->mysql_thd != nullptr &&
+          ut_difftime(withdraw_started, trx->start_time) > 0) {
+        if (!found) {
+          ib::warn(ER_IB_MSG_61) << "The following trx might hold"
+                                    " the blocks in buffer pool to"
+                                    " be withdrawn. Buffer pool"
+                                    " resizing can complete only"
+                                    " after all the transactions"
+                                    " below release the blocks.";
+          found = true;
+        }
+
+        lock_trx_print_wait_and_mvcc_state(stderr, trx);
+      }
+    }
+#else
     bool found = false;
     for (trx_t *trx = UT_LIST_GET_FIRST(trx_sys->mysql_trx_list); trx != NULL;
          trx = UT_LIST_GET_NEXT(mysql_trx_list, trx)) {
@@ -2161,6 +2181,7 @@ withdraw_retry:
         lock_trx_print_wait_and_mvcc_state(stderr, trx);
       }
     }
+#endif /* HAVE_LOCK_FREE_READVIEW */
     trx_sys_mutex_exit();
     lock_mutex_exit();
 
diff --git a/storage/innobase/clone/clone0copy.cc b/storage/innobase/clone/clone0copy.cc
index b18c8b4..4dc0092 100644
--- a/storage/innobase/clone/clone0copy.cc
+++ b/storage/innobase/clone/clone0copy.cc
@@ -327,14 +327,22 @@ int Clone_Snapshot::update_binlog_position() {
 }
 
 int Clone_Snapshot::wait_trx_end(THD *thd, trx_id_t trx_id) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+  auto trx = trx_sys->find(current_trx(), trx_id, false);
+#else
   auto trx = trx_rw_is_active(trx_id, nullptr, false);
+#endif
   if (trx == nullptr) {
     return (0);
   }
 
   auto wait_cond = [&](bool alert, bool &result) {
     /* Check if transaction is still active. */
+#ifdef HAVE_LOCK_FREE_READVIEW
+    auto trx = trx_sys->find(current_trx(), trx_id, false);
+#else
     auto trx = trx_rw_is_active(trx_id, nullptr, false);
+#endif
     if (trx == nullptr) {
       result = false;
       return (0);
diff --git a/storage/innobase/clone/clone0repl.cc b/storage/innobase/clone/clone0repl.cc
index 8b9484c..61928e5 100644
--- a/storage/innobase/clone/clone0repl.cc
+++ b/storage/innobase/clone/clone0repl.cc
@@ -49,7 +49,9 @@ void Clone_persist_gtid::add(const Gtid_desc &gtid_desc) {
   if (!is_active() || gtid_table_persistor == nullptr) {
     return;
   }
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(trx_sys_mutex_own());
+#endif
   /* Get active GTID list */
   auto &current_gtids = get_active_list();
 
diff --git a/storage/innobase/dict/dict0dd.cc b/storage/innobase/dict/dict0dd.cc
index 01631fd..df40c27 100644
--- a/storage/innobase/dict/dict0dd.cc
+++ b/storage/innobase/dict/dict0dd.cc
@@ -6009,9 +6009,15 @@ the dictionary.
 bool dd_tablespace_update_cache(THD *thd) {
   /* If there are no prepared trxs, then DD reads would have been
   already consistent. No need to update cache */
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (!trx_sys->found_prepared_trx()) {
+    return (false);
+  }
+#else
   if (!trx_sys->found_prepared_trx) {
     return (false);
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   dd::cache::Dictionary_client *dc = dd::get_dd_client(thd);
   dd::cache::Dictionary_client::Auto_releaser releaser(dc);
diff --git a/storage/innobase/dict/dict0mem.cc b/storage/innobase/dict/dict0mem.cc
index 02bccdc..8e715e2 100644
--- a/storage/innobase/dict/dict0mem.cc
+++ b/storage/innobase/dict/dict0mem.cc
@@ -596,9 +596,14 @@ bool dict_index_t::is_usable(const trx_t *trx) const {
   }
 
   /* Check if the specified transaction can see this index. */
+#ifdef HAVE_LOCK_FREE_READVIEW
+  return (table->is_temporary() || trx_id == 0 || !trx->read_view.is_open() ||
+          trx->read_view.changes_visible(trx_id, table->name));
+#else
   return (table->is_temporary() || trx_id == 0 ||
           !MVCC::is_view_active(trx->read_view) ||
           trx->read_view->changes_visible(trx_id, table->name));
+#endif /* HAVE_LOCK_FREE_READVIEW */
 }
 #endif /* !UNIV_HOTBACKUP */
 
diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc
index 75c2b84..9592b5a 100644
--- a/storage/innobase/handler/ha_innodb.cc
+++ b/storage/innobase/handler/ha_innodb.cc
@@ -671,6 +671,9 @@ static PSI_mutex_info all_innodb_mutexes[] = {
     PSI_MUTEX_KEY(rtr_match_mutex, 0, 0, PSI_DOCUMENT_ME),
     PSI_MUTEX_KEY(rtr_path_mutex, 0, 0, PSI_DOCUMENT_ME),
     PSI_MUTEX_KEY(rtr_ssn_mutex, 0, 0, PSI_DOCUMENT_ME),
+#ifdef HAVE_LOCK_FREE_READVIEW
+    PSI_MUTEX_KEY(rw_trx_hash_element_mutex, 0, 0, PSI_DOCUMENT_ME),
+#endif /* HAVE_LOCK_FREE_READVIEW */
     PSI_MUTEX_KEY(trx_sys_mutex, 0, 0, PSI_DOCUMENT_ME),
     PSI_MUTEX_KEY(zip_pad_mutex, 0, 0, PSI_DOCUMENT_ME),
     PSI_MUTEX_KEY(master_key_id_mutex, 0, 0, PSI_DOCUMENT_ME),
@@ -1059,6 +1062,11 @@ static SHOW_VAR innodb_status_variables[] = {
     {"undo_tablespaces_active",
      (char *)&export_vars.innodb_undo_tablespaces_active, SHOW_LONG,
      SHOW_SCOPE_GLOBAL},
+#ifdef HAVE_LOCK_FREE_READVIEW
+     {"have_lock_free_readview",
+     (char *)&export_vars.innodb_have_lock_free_readview, SHOW_BOOL,
+     SHOW_SCOPE_GLOBAL},
+#endif
 #ifdef UNIV_DEBUG
     {"purge_trx_id_age", (char *)&export_vars.innodb_purge_trx_id_age,
      SHOW_LONG, SHOW_SCOPE_GLOBAL},
@@ -2865,6 +2873,20 @@ ibool trx_is_strict(trx_t *trx) /*!< in: transaction */
           (!trx->in_truncate));
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** Gets current trx.
+
+ This function may be called during InnoDB initialisation, when
+ innodb_hton_ptr->slot is not yet set to meaningful value. */
+trx_t *current_trx() {
+  THD *thd = current_thd;
+  if (UNIV_LIKELY(thd != nullptr) && innodb_hton_ptr->slot != HA_SLOT_UNDEF) {
+    return thd_to_trx(thd);
+  }
+  return nullptr;
+}
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
 /** Resets some fields of a m_prebuilt struct. The template is used in fast
  retrieval of just those column values MySQL needs in its processing. */
 void ha_innobase::reset_template(void) {
@@ -2918,7 +2940,11 @@ void ha_innobase::init_table_handle_for_HANDLER(void) {
 
   /* Assign a read view if the transaction does not have it yet */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  m_prebuilt->trx->read_view.open(m_prebuilt->trx);
+#else
   trx_assign_read_view(m_prebuilt->trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   innobase_register_trx(ht, m_user_thd, m_prebuilt->trx);
 
@@ -4939,10 +4965,17 @@ static int innobase_init_files(dict_init_mode_t dict_init_mode,
       return innodb_init_abort();
     }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    if (trx_sys->found_prepared_trx() > 0) {
+      ib::error(ER_DD_UPGRADE_FOUND_PREPARED_XA_TRANSACTION);
+      return innodb_init_abort();
+    }
+#else
     if (trx_sys->found_prepared_trx > 0) {
       ib::error(ER_DD_UPGRADE_FOUND_PREPARED_XA_TRANSACTION);
       return innodb_init_abort();
     }
+#endif
 
     /* Disable AHI when we start loading tables for purge.
     These tables are evicted anyway after purge. */
@@ -5185,7 +5218,11 @@ static int innobase_start_trx_and_assign_read_view(
       innobase_map_isolation_level(thd_get_trx_isolation(thd));
 
   if (trx->isolation_level == TRX_ISO_REPEATABLE_READ) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx->read_view.open(trx);
+#else
     trx_assign_read_view(trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   } else {
     push_warning_printf(thd, Sql_condition::SL_WARNING, HA_ERR_UNSUPPORTED,
                         "InnoDB: WITH CONSISTENT SNAPSHOT"
@@ -10091,7 +10128,11 @@ int ha_innobase::sample_init(void *&scan_ctx, double sampling_percentage,
   auto trx = m_prebuilt->trx;
   innobase_register_trx(ht, ha_thd(), trx);
   trx_start_if_not_started_xa(trx, false);
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx->read_view.open(trx);
+#else
   trx_assign_read_view(trx);
+#endif
 
   /* Parallel read is not currently supported for sampling. */
   size_t n_threads = 1;
@@ -17774,7 +17815,10 @@ int ha_innobase::external_lock(THD *thd, /*!< in: handle to the user thread */
         trx->is_dd_trx here */
         ut_d(trx->is_dd_trx = false);
       }
-
+#ifdef HAVE_LOCK_FREE_READVIEW
+    } else if (trx->isolation_level <= TRX_ISO_READ_COMMITTED) {
+      trx->read_view.close();
+#else
     } else if (trx->isolation_level <= TRX_ISO_READ_COMMITTED &&
                MVCC::is_view_active(trx->read_view)) {
       mutex_enter(&trx_sys->mutex);
@@ -17782,6 +17826,7 @@ int ha_innobase::external_lock(THD *thd, /*!< in: handle to the user thread */
       trx_sys->mvcc->view_close(trx->read_view, true);
 
       mutex_exit(&trx_sys->mutex);
+#endif /* HAVE_LOCK_FREE_READVIEW */
     }
   }
 
@@ -18377,6 +18422,14 @@ THR_LOCK_DATA **ha_innobase::store_lock(
     trx->isolation_level =
         innobase_map_isolation_level((enum_tx_isolation)thd_tx_isolation(thd));
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    if (trx->isolation_level <= TRX_ISO_READ_COMMITTED) {
+      /* At low transaction isolation levels we let
+      each consistent read set its own snapshot */
+
+      trx->read_view.close();
+    }
+#else
     if (trx->isolation_level <= TRX_ISO_READ_COMMITTED &&
         MVCC::is_view_active(trx->read_view)) {
       /* At low transaction isolation levels we let
@@ -18388,6 +18441,7 @@ THR_LOCK_DATA **ha_innobase::store_lock(
 
       mutex_exit(&trx_sys->mutex);
     }
+#endif /* HAVE_LOCK_FREE_READVIEW */
   }
 
   DBUG_ASSERT(EQ_CURRENT_THD(thd));
diff --git a/storage/innobase/handler/ha_innopart.cc b/storage/innobase/handler/ha_innopart.cc
index e684f4e..ffa06b7 100644
--- a/storage/innobase/handler/ha_innopart.cc
+++ b/storage/innobase/handler/ha_innopart.cc
@@ -2031,7 +2031,11 @@ int ha_innopart::sample_init(void *&scan_ctx, double sampling_percentage,
   auto trx = m_prebuilt->trx;
   innobase_register_trx(ht, ha_thd(), trx);
   trx_start_if_not_started_xa(trx, false);
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx->read_view.open(trx);
+#else
   trx_assign_read_view(trx);
+#endif
 
   /* Parallel read is not currently supported for sampling. */
   size_t n_threads = 1;
@@ -3178,7 +3182,11 @@ int ha_innopart::records(ha_rows *num_rows) {
       trx->mysql_n_tables_locked == 0 && !m_prebuilt->ins_sel_stmt &&
       n_threads > 1) {
     trx_start_if_not_started_xa(trx, false);
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx->read_view.open(trx);
+#else
     trx_assign_read_view(trx);
+#endif
 
     const auto first_used_partition = m_part_info->get_first_used_partition();
 
diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc
index dfb5d0f..7bc1384 100644
--- a/storage/innobase/handler/handler0alter.cc
+++ b/storage/innobase/handler/handler0alter.cc
@@ -1173,7 +1173,11 @@ int ha_innobase::parallel_scan_init(void *&scan_ctx, size_t &num_threads) {
 
   trx_start_if_not_started_xa(trx, false);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx->read_view.open(trx);
+#else
   trx_assign_read_view(trx);
+#endif
 
   size_t n_threads = thd_parallel_read_threads(m_prebuilt->trx->mysql_thd);
 
@@ -4778,7 +4782,11 @@ static MY_ATTRIBUTE((warn_unused_result)) bool prepare_inplace_alter_table_dict(
   if (ctx->online) {
     /* Assign a consistent read view for
     row_merge_read_clustered_index(). */
+#ifdef HAVE_LOCK_FREE_READVIEW
+    ctx->prebuilt->trx->read_view.open(ctx->prebuilt->trx);
+#else
     trx_assign_read_view(ctx->prebuilt->trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   }
 
   if (fts_index) {
@@ -9750,7 +9758,11 @@ int ha_innopart::parallel_scan_init(void *&scan_ctx, size_t &num_threads) {
 
   trx_start_if_not_started_xa(trx, false);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx->read_view.open(trx);
+#else
   trx_assign_read_view(trx);
+#endif
 
   const Parallel_reader::Scan_range FULL_SCAN{};
 
diff --git a/storage/innobase/handler/p_s.cc b/storage/innobase/handler/p_s.cc
index 74c9f94..b3fe28e 100644
--- a/storage/innobase/handler/p_s.cc
+++ b/storage/innobase/handler/p_s.cc
@@ -143,6 +143,11 @@ this program; if not, write to the Free Software Foundation, Inc.,
 static const char *g_engine = "INNODB";
 static const size_t g_engine_length = 6;
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+inline trx_t *get_next_trx(const trx_t *trx) {
+  return (UT_LIST_GET_NEXT(trx_list, trx));
+}
+#else
 inline trx_t *get_next_trx(const trx_t *trx, bool read_write) {
   if (read_write) {
     return (UT_LIST_GET_NEXT(trx_list, trx));
@@ -150,10 +155,17 @@ inline trx_t *get_next_trx(const trx_t *trx, bool read_write) {
     return (UT_LIST_GET_NEXT(mysql_trx_list, trx));
   }
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Pass of a given scan. */
 enum scan_pass {
   INIT_SCANNING,
+#ifdef HAVE_LOCK_FREE_READVIEW
+  /** Scan the trx list.
+  @sa trx_sys_t::trx_list
+  */
+  SCANNING_TRX_LIST,
+#else
   /** Scan the RW trx list.
   @sa trx_sys_t::rw_trx_list
   */
@@ -162,6 +174,7 @@ enum scan_pass {
   @sa trx_t::mysql_trx_list
   */
   SCANNING_MYSQL_TRX_LIST,
+#endif /* HAVE_LOCK_FREE_READVIEW */
   DONE_SCANNING
 };
 
@@ -202,6 +215,17 @@ class Innodb_trx_scan_state {
       m_next_trx_id_range = TRX_ID_MAX;
     } else {
       switch (m_scan_pass) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+        case INIT_SCANNING:
+          m_scan_pass = SCANNING_TRX_LIST;
+          m_start_trx_id_range = 0;
+          m_end_trx_id_range = SCAN_RANGE;
+          m_next_trx_id_range = TRX_ID_MAX;
+          break;
+        case SCANNING_TRX_LIST:
+          m_scan_pass = DONE_SCANNING;
+          break;
+#else
         case INIT_SCANNING:
           m_scan_pass = SCANNING_RW_TRX_LIST;
           m_start_trx_id_range = 0;
@@ -217,6 +241,7 @@ class Innodb_trx_scan_state {
         case SCANNING_MYSQL_TRX_LIST:
           m_scan_pass = DONE_SCANNING;
           break;
+#endif /* HAVE_LOCK_FREE_READVIEW */
         case DONE_SCANNING:
         default:
           ut_error;
@@ -350,6 +375,41 @@ class Innodb_data_lock_wait_iterator
   Innodb_trx_scan_state m_scan_state;
 };
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** Check if a transaction should be discarded.
+Transactions present in the trx list that have not started yet
+are discarded, when inspecting data locks.
+@param[in] trx Transaction to evaluate
+@returns True if the trx should be discarded
+*/
+bool discard_trx(const trx_t *trx) { return !trx_is_started(trx); }
+
+/** Find a transaction in a TRX LIST.
+@param[in] trx_id	The transaction id
+@param[in] trx_list	The transaction list
+@returns The transaction when found, or NULL
+*/
+static const trx_t *fetch_trx_in_trx_list(uint64_t filter_trx_immutable_id,
+                                          trx_ut_list_t *trx_list) {
+  const trx_t *trx;
+
+  ut_ad(lock_mutex_own());
+  ut_ad(trx_sys->mutex.is_owned());
+
+  for (trx = UT_LIST_GET_FIRST(*trx_list); trx != NULL;
+       trx = get_next_trx(trx)) {
+    if (discard_trx(trx)) {
+      continue;
+    }
+
+    if (filter_trx_immutable_id == trx_immutable_id(trx)) {
+      return trx;
+    }
+  }
+
+  return nullptr;
+}
+#else
 /** Check if a transaction should be discarded.
 Transactions present in any TRX LIST that have not started yet
 are discarded, when inspecting data locks.
@@ -398,6 +458,7 @@ static const trx_t *fetch_trx_in_trx_list(uint64_t filter_trx_immutable_id,
 
   return NULL;
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 Innodb_data_lock_inspector::Innodb_data_lock_inspector() {}
 
@@ -586,6 +647,16 @@ bool Innodb_data_lock_iterator::scan(PSI_server_data_lock_container *container,
   lock_mutex_enter();
 
   trx_sys_mutex_enter();
+#ifdef HAVE_LOCK_FREE_READVIEW
+
+  size_t found = 0;
+
+  while ((m_scan_state.get_pass() == SCANNING_TRX_LIST) && (found == 0)) {
+    found = scan_trx_list(container, with_lock_data, true, &trx_sys->trx_list);
+    m_scan_state.prepare_next_scan();
+  }
+
+#else
 
   size_t found = 0;
 
@@ -601,6 +672,7 @@ bool Innodb_data_lock_iterator::scan(PSI_server_data_lock_container *container,
     m_scan_state.prepare_next_scan();
   }
 
+#endif /* HAVE_LOCK_FREE_READVIEW */
   trx_sys_mutex_exit();
 
   lock_mutex_exit();
@@ -632,6 +704,15 @@ bool Innodb_data_lock_iterator::fetch(PSI_server_data_lock_container *container,
   lock_mutex_enter();
 
   trx_sys_mutex_enter();
+#ifdef HAVE_LOCK_FREE_READVIEW
+
+  trx = fetch_trx_in_trx_list(trx_immutable_id, &trx_sys->trx_list);
+
+  if (trx != nullptr) {
+    scan_trx(container, with_lock_data, trx, true, lock_immutable_id, heap_id);
+  }
+
+#else
 
   trx = fetch_trx_in_trx_list(trx_immutable_id, true, &trx_sys->rw_trx_list);
 
@@ -644,6 +725,7 @@ bool Innodb_data_lock_iterator::fetch(PSI_server_data_lock_container *container,
     scan_trx(container, with_lock_data, trx, true, lock_immutable_id, heap_id);
   }
 
+#endif /* HAVE_LOCK_FREE_READVIEW */
   trx_sys_mutex_exit();
 
   lock_mutex_exit();
@@ -667,6 +749,24 @@ size_t Innodb_data_lock_iterator::scan_trx_list(
   size_t found = 0;
 
   ut_ad(lock_mutex_own());
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad(trx_sys->mutex.is_owned());
+
+  for (trx = UT_LIST_GET_FIRST(*trx_list); trx != nullptr;
+       trx = get_next_trx(trx)) {
+    if (discard_trx(trx)) {
+      continue;
+    }
+
+    trx_id = trx_get_id_for_print(trx);
+
+    if (!m_scan_state.trx_id_in_range(trx_id)) {
+      continue;
+    }
+
+    found += scan_trx(container, with_lock_data, trx, false);
+  }
+#else
   ut_ad(trx_sys_mutex_own());
 
   for (trx = UT_LIST_GET_FIRST(*trx_list); trx != NULL;
@@ -683,6 +783,7 @@ size_t Innodb_data_lock_iterator::scan_trx_list(
 
     found += scan_trx(container, with_lock_data, trx, false);
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   return found;
 }
@@ -859,6 +960,16 @@ bool Innodb_data_lock_wait_iterator::scan(
   lock_mutex_enter();
 
   trx_sys_mutex_enter();
+#ifdef HAVE_LOCK_FREE_READVIEW
+
+  size_t found = 0;
+
+  while ((m_scan_state.get_pass() == SCANNING_TRX_LIST) && (found == 0)) {
+    found = scan_trx_list(container, true, &trx_sys->trx_list);
+    m_scan_state.prepare_next_scan();
+  }
+
+#else
 
   size_t found = 0;
 
@@ -872,6 +983,7 @@ bool Innodb_data_lock_wait_iterator::scan(
     m_scan_state.prepare_next_scan();
   }
 
+#endif /* HAVE_LOCK_FREE_READVIEW */
   trx_sys_mutex_exit();
 
   lock_mutex_exit();
@@ -918,6 +1030,16 @@ bool Innodb_data_lock_wait_iterator::fetch(
   lock_mutex_enter();
 
   trx_sys_mutex_enter();
+#ifdef HAVE_LOCK_FREE_READVIEW
+
+  trx = fetch_trx_in_trx_list(requesting_trx_immutable_id, &trx_sys->trx_list);
+
+  if (trx != nullptr) {
+    scan_trx(container, trx, true, requesting_lock_immutable_id,
+             blocking_lock_immutable_id);
+  }
+
+#else
 
   trx = fetch_trx_in_trx_list(requesting_trx_immutable_id, true,
                               &trx_sys->rw_trx_list);
@@ -932,6 +1054,7 @@ bool Innodb_data_lock_wait_iterator::fetch(
              blocking_lock_immutable_id);
   }
 
+#endif /* HAVE_LOCK_FREE_READVIEW */
   trx_sys_mutex_exit();
 
   lock_mutex_exit();
@@ -953,6 +1076,24 @@ size_t Innodb_data_lock_wait_iterator::scan_trx_list(
   size_t found = 0;
 
   ut_ad(lock_mutex_own());
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad(trx_sys->mutex.is_owned());
+
+  for (trx = UT_LIST_GET_FIRST(*trx_list); trx != nullptr;
+       trx = get_next_trx(trx)) {
+    if (discard_trx(trx)) {
+      continue;
+    }
+
+    trx_id = trx_get_id_for_print(trx);
+
+    if (!m_scan_state.trx_id_in_range(trx_id)) {
+      continue;
+    }
+
+    found += scan_trx(container, trx, false);
+  }
+#else
   ut_ad(trx_sys_mutex_own());
 
   for (trx = UT_LIST_GET_FIRST(*trx_list); trx != NULL;
@@ -969,6 +1110,7 @@ size_t Innodb_data_lock_wait_iterator::scan_trx_list(
 
     found += scan_trx(container, trx, false);
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   return found;
 }
diff --git a/storage/innobase/include/clone0repl.h b/storage/innobase/include/clone0repl.h
index 625b6e0..92d0275 100644
--- a/storage/innobase/include/clone0repl.h
+++ b/storage/innobase/include/clone0repl.h
@@ -235,7 +235,9 @@ class Clone_persist_gtid {
 
   /** @return current active GTID list */
   Gitd_info_list &get_active_list() {
+#ifndef HAVE_LOCK_FREE_READVIEW
     ut_ad(trx_sys_mutex_own());
+#endif
     return (get_list(m_active_number));
   }
 
@@ -292,7 +294,9 @@ class Clone_persist_gtid {
   /** Switch active GTID list. */
   uint64_t switch_active_list() {
     /* Switch active list under transaction system mutex. */
+#ifndef HAVE_LOCK_FREE_READVIEW
     ut_ad(trx_sys_mutex_own());
+#endif
     uint64_t flush_number = m_active_number;
     ++m_active_number;
     m_compression_gtid_counter += m_num_gtid_mem;
diff --git a/storage/innobase/include/read0read.h b/storage/innobase/include/read0read.h
index eab38c4..49cdc79 100644
--- a/storage/innobase/include/read0read.h
+++ b/storage/innobase/include/read0read.h
@@ -33,6 +33,8 @@ this program; if not, write to the Free Software Foundation, Inc.,
 #ifndef read0read_h
 #define read0read_h
 
+#ifndef HAVE_LOCK_FREE_READVIEW
+
 #include <stddef.h>
 #include <algorithm>
 
@@ -128,4 +130,5 @@ class MVCC {
   view_list_t m_views;
 };
 
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #endif /* read0read_h */
diff --git a/storage/innobase/include/read0types.h b/storage/innobase/include/read0types.h
index 973f101..331c304 100644
--- a/storage/innobase/include/read0types.h
+++ b/storage/innobase/include/read0types.h
@@ -38,13 +38,25 @@ this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "trx0types.h"
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** View is not visible to purge thread. */
+constexpr int32_t READ_VIEW_STATE_CLOSED = 0;
+
+/** View is being opened, purge thread must wait for state change. */
+constexpr int32_t READ_VIEW_STATE_SNAPSHOT = 1;
+
+/** View is visible to purge thread. */
+constexpr int32_t READ_VIEW_STATE_OPEN = 2;
+#else
 // Friend declaration
 class MVCC;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Read view lists the trx ids of those transactions for which a consistent
 read should not see the modifications to the database. */
 
 class ReadView {
+#ifndef HAVE_LOCK_FREE_READVIEW
   /** This is similar to a std::vector but it is not a drop
   in replacement. It is specific to ReadView. */
   class ids_t {
@@ -148,6 +160,7 @@ class ReadView {
 
     friend class ReadView;
   };
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
  public:
   ReadView();
@@ -178,9 +191,13 @@ class ReadView {
       return (true);
     }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    return (!std::binary_search(m_ids.begin(), m_ids.end(), id));
+#else
     const ids_t::value_type *p = m_ids.data();
 
     return (!std::binary_search(p, p + m_ids.size(), id));
+#endif /* HAVE_LOCK_FREE_READVIEW */
   }
 
   /**
@@ -188,16 +205,62 @@ class ReadView {
   @return true if view sees transaction id */
   bool sees(trx_id_t id) const { return (id < m_up_limit_id); }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  /** Creates a snapshot where exactly the transaction serialized before this
+  point in time are seen in the view.
+
+  @param[in, out] trx transaction */
+  void snapshot(trx_t *trx);
+
+  /** Open a read view where exactly the transaction serialized before this
+  point in time are seen in the view.
+
+  View become visible to purge thread.
+
+  @param[in,out] trx transaction */
+  void open(trx_t *trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   /**
   Mark the view as closed */
   void close() {
+#ifdef HAVE_LOCK_FREE_READVIEW
+    int32_t state = m_state.load(std::memory_order_relaxed);
+    ut_ad(state == READ_VIEW_STATE_CLOSED || state == READ_VIEW_STATE_OPEN);
+    if (state == READ_VIEW_STATE_OPEN) {
+      m_state.store(READ_VIEW_STATE_CLOSED, std::memory_order_relaxed);
+    }
+#else
     ut_ad(m_creator_trx_id != TRX_ID_MAX);
     m_creator_trx_id = TRX_ID_MAX;
+#endif /* HAVE_LOCK_FREE_READVIEW */
   }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  /** m_state getter fir trx_sys::clone_oldest_view() & trx_sys::size(). */
+  int32_t get_state() const { return m_state.load(std::memory_order_acquire); }
+
+  /** Returns ture if view is open.
+
+  Only used by view owner thread, thus we can omit atomic operations. */
+  bool is_open() const {
+    int32_t state = m_state.load(std::memory_order_relaxed);
+    ut_ad(state == READ_VIEW_STATE_OPEN || state == READ_VIEW_STATE_CLOSED);
+    return (state == READ_VIEW_STATE_OPEN);
+  }
+
+  /** Set the creator transaction id.
+
+  This should be set only for views created by RW transactions. */
+  void set_creator_trx_id(trx_id_t id) {
+    ut_ad(id > 0);
+    ut_ad(m_creator_trx_id == 0);
+    m_creator_trx_id = id;
+  }
+#else
   /**
   @return true if the view is closed */
   bool is_closed() const { return (m_closed); }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /**
   Write the limits to the file.
@@ -244,7 +307,11 @@ class ReadView {
     return (m_low_limit_no <= rhs->m_low_limit_no);
   }
 #endif /* UNIV_DEBUG */
+#ifdef HAVE_LOCK_FREE_READVIEW
+  void copy(const ReadView &other);
+#endif /* HAVE_LOCK_FREE_READVIEW */
  private:
+#ifndef HAVE_LOCK_FREE_READVIEW
   /**
   Copy the transaction ids from the source vector */
   inline void copy_trx_ids(const trx_ids_t &trx_ids);
@@ -273,6 +340,7 @@ class ReadView {
   }
 
   friend class MVCC;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
  private:
   // Disable copying
@@ -295,7 +363,11 @@ class ReadView {
 
   /** Set of RW transactions that was active when this snapshot
   was taken */
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_ids_t m_ids;
+#else
   ids_t m_ids;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /** The view does not need to see the undo logs for transactions
   whose transaction number is strictly smaller (<) than this value:
@@ -310,6 +382,25 @@ class ReadView {
   trx_id_t m_view_low_limit_no;
 #endif /* UNIV_DEBUG */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  /** View state.
+
+  It is not defined as enum as it has to be updated using atomic operations.
+  Possible values are READ_VIEW_STATE_CLOSED, READ_VIEW_STATE_SNAPSHOT and
+  READ_VIEW_STATE_OPEN.
+
+  Possible state transfers...
+
+  Start view open:
+  READ_VIEW_STATE_CLOSED -> READ_VIEW_STATE_SNAPSHOT
+
+  Complete view open:
+  READ_VIEW_STATE_SNAPSHOT -> READ_VIEW_STATE_OPEN
+
+  Close view:
+  READ_VIEW_STATE_OPEN -> READ_VIEW_STATE_CLOSED */
+  std::atomic<int32_t> m_state;
+#else
   /** AC-NL-RO transaction view that has been "closed". */
   bool m_closed;
 
@@ -318,6 +409,7 @@ class ReadView {
   /** List of read views in trx_sys */
   byte pad1[64 - sizeof(node_t)];
   node_t m_view_list;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 };
 
 #endif
diff --git a/storage/innobase/include/row0vers.h b/storage/innobase/include/row0vers.h
index e8bf136..68fde81 100644
--- a/storage/innobase/include/row0vers.h
+++ b/storage/innobase/include/row0vers.h
@@ -53,6 +53,9 @@ class ReadView;
  negatives. The caller must confirm all positive results by calling
  trx_is_active() while holding lock_sys->mutex. */
 trx_t *row_vers_impl_x_locked(
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_t *caller_trx,     /*!< in/out: trx of current thread */
+#endif                     /* HAVE_LOCK_FREE_READVIEW */
     const rec_t *rec,          /*!< in: record in a secondary index */
     const dict_index_t *index, /*!< in: the secondary index */
     const ulint *offsets);     /*!< in: rec_get_offsets(rec, index) */
@@ -116,6 +119,9 @@ dberr_t row_vers_build_for_consistent_read(
 /** Constructs the last committed version of a clustered index record,
  which should be seen by a semi-consistent read. */
 void row_vers_build_for_semi_consistent_read(
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_t *caller_trx,        /*!< in/out: trx of current thread */
+#endif                        /* HAVE_LOCK_FREE_READVIEW */
     const rec_t *rec,         /*!< in: record in a clustered index; the
                               caller must have a latch on the page; this
                               latch locks the top of the stack of versions
diff --git a/storage/innobase/include/srv0srv.h b/storage/innobase/include/srv0srv.h
index 738f486..90a34d6 100644
--- a/storage/innobase/include/srv0srv.h
+++ b/storage/innobase/include/srv0srv.h
@@ -1149,6 +1149,9 @@ struct export_var_t {
                                           the dba created explicitly. */
   ulint innodb_undo_tablespaces_active;   /*!< number of active undo
                                           tablespaces */
+#ifdef HAVE_LOCK_FREE_READVIEW
+  bool innodb_have_lock_free_readview;
+#endif
 #ifdef UNIV_DEBUG
   ulint innodb_purge_trx_id_age;      /*!< rw_max_trx_no - purged trx_no */
   ulint innodb_purge_view_trx_id_age; /*!< rw_max_trx_no
diff --git a/storage/innobase/include/sync0sync.h b/storage/innobase/include/sync0sync.h
index ed8449e..85b9fea 100644
--- a/storage/innobase/include/sync0sync.h
+++ b/storage/innobase/include/sync0sync.h
@@ -158,6 +158,9 @@ extern mysql_pfs_key_t trx_pool_manager_mutex_key;
 extern mysql_pfs_key_t temp_pool_manager_mutex_key;
 extern mysql_pfs_key_t lock_mutex_key;
 extern mysql_pfs_key_t lock_wait_mutex_key;
+#ifdef HAVE_LOCK_FREE_READVIEW
+extern mysql_pfs_key_t rw_trx_hash_element_mutex_key;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 extern mysql_pfs_key_t trx_sys_mutex_key;
 extern mysql_pfs_key_t srv_sys_mutex_key;
 extern mysql_pfs_key_t srv_threads_mutex_key;
diff --git a/storage/innobase/include/sync0types.h b/storage/innobase/include/sync0types.h
index 0ee4353..b925b8a 100644
--- a/storage/innobase/include/sync0types.h
+++ b/storage/innobase/include/sync0types.h
@@ -267,6 +267,9 @@ enum latch_level_t {
   SYNC_REC_LOCK,
   SYNC_THREADS,
   SYNC_TRX,
+#ifdef HAVE_LOCK_FREE_READVIEW
+  SYNC_RW_TRX_HASH_ELEMENT,
+#endif /* HAVE_LOCK_FREE_READVIEW */
   SYNC_POOL,
   SYNC_POOL_MANAGER,
   SYNC_TRX_SYS,
@@ -452,6 +455,9 @@ enum latch_id_t {
   LATCH_ID_CLONE_SYS,
   LATCH_ID_CLONE_TASK,
   LATCH_ID_CLONE_SNAPSHOT,
+#ifdef HAVE_LOCK_FREE_READVIEW
+  LATCH_ID_RW_TRX_HASH_ELEMENT,
+#endif /* HAVE_LOCK_FREE_READVIEW */
   LATCH_ID_PARALLEL_READ,
   LATCH_ID_REDO_LOG_ARCHIVE_ADMIN_MUTEX,
   LATCH_ID_REDO_LOG_ARCHIVE_QUEUE_MUTEX,
diff --git a/storage/innobase/include/trx0roll.h b/storage/innobase/include/trx0roll.h
index 83c13a9..b6312ed 100644
--- a/storage/innobase/include/trx0roll.h
+++ b/storage/innobase/include/trx0roll.h
@@ -61,6 +61,15 @@ trx_undo_rec_t *trx_roll_pop_top_rec_of_trx(
     roll_ptr_t *roll_ptr, /*!< out: roll pointer to undo record */
     mem_heap_t *heap);    /*!< in: memory heap where copied */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** Rollback or clean up any incomplete transactions which were
+ encountered in crash recovery.  If the transaction already was
+ committed, then we clean up a possible insert undo log. If the
+ transaction was not yet committed, then we roll it back.
+ @param all true=roll back all recovered active transactions;
+ false=roll back any incomplete dictionary transaction */
+void trx_rollback_recovered(bool all);
+#else
 /** Rollback or clean up any incomplete transactions which were
  encountered in crash recovery.  If the transaction already was
  committed, then we clean up a possible insert undo log. If the
@@ -68,6 +77,7 @@ trx_undo_rec_t *trx_roll_pop_top_rec_of_trx(
 void trx_rollback_or_clean_recovered(
     ibool all); /*!< in: FALSE=roll back dictionary transactions;
                 TRUE=roll back all non-PREPARED transactions */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Rollback or clean up any incomplete transactions which were
 encountered in crash recovery.  If the transaction already was
diff --git a/storage/innobase/include/trx0sys.h b/storage/innobase/include/trx0sys.h
index d7cc4ed..eafe593 100644
--- a/storage/innobase/include/trx0sys.h
+++ b/storage/innobase/include/trx0sys.h
@@ -35,6 +35,10 @@ this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "univ.i"
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+#include "lf.h"
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
 #include "buf0buf.h"
 #include "fil0fil.h"
 #include "trx0types.h"
@@ -54,7 +58,9 @@ this program; if not, write to the Free Software Foundation, Inc.,
 typedef UT_LIST_BASE_NODE_T(trx_t) trx_ut_list_t;
 
 // Forward declaration
+#ifndef HAVE_LOCK_FREE_READVIEW
 class MVCC;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 class ReadView;
 
 /** The transaction system */
@@ -130,6 +136,7 @@ UNIV_INLINE
 void trx_sysf_rseg_set_page_no(trx_sysf_t *sys_header, ulint i,
                                page_no_t page_no, mtr_t *mtr);
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** Allocates a new transaction id.
  @return new, allocated trx id */
 UNIV_INLINE
@@ -139,6 +146,7 @@ trx_id_t trx_sys_get_new_trx_id();
  next call to trx_sys_get_new_trx_id() */
 UNIV_INLINE
 trx_id_t trx_sys_get_max_trx_id(void);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 #ifdef UNIV_DEBUG
 /* Flag to control TRX_RSEG_N_SLOTS behavior debugging. */
@@ -162,6 +170,7 @@ UNIV_INLINE
 trx_id_t trx_read_trx_id(
     const byte *ptr); /*!< in: pointer to memory from where to read */
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** Looks for the trx instance with the given id in the rw trx_list.
  @return	the trx handle or NULL if not found */
 UNIV_INLINE
@@ -206,6 +215,7 @@ UNIV_INLINE
 ibool trx_assert_recovered(trx_id_t trx_id) /*!< in: transaction identifier */
     MY_ATTRIBUTE((warn_unused_result));
 #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Persist transaction number limit below which all transaction GTIDs
 are persisted to disk table.
@@ -246,6 +256,7 @@ void trx_sys_update_mysql_binlog_offset(trx_t *trx, mtr_t *mtr);
 /** Shutdown/Close the transaction system. */
 void trx_sys_close(void);
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** Determine if there are incomplete transactions in the system.
 @return whether incomplete transactions need rollback */
 UNIV_INLINE
@@ -255,7 +266,9 @@ bool trx_sys_need_rollback();
 Check if there are any active (non-prepared) transactions.
 @return total number of active transactions or 0 if none */
 ulint trx_sys_any_active_transactions(void);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #endif /* !UNIV_HOTBACKUP */
+#ifndef HAVE_LOCK_FREE_READVIEW
 /**
 Add the transaction to the RW transaction set
 @param trx		transaction instance to add */
@@ -267,6 +280,7 @@ void trx_sys_rw_trx_add(trx_t *trx);
  @return true if the list is valid */
 bool trx_sys_validate_trx_list();
 #endif /* UNIV_DEBUG */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Initialize trx_sys_undo_spaces, called once during srv_start(). */
 void trx_sys_undo_spaces_init();
@@ -420,6 +434,509 @@ class Space_Ids : public std::vector<space_id_t, ut_allocator<space_id_t>> {
 };
 
 #ifndef UNIV_HOTBACKUP
+#ifdef HAVE_LOCK_FREE_READVIEW
+trx_t *current_trx();
+
+struct rw_trx_hash_element_t {
+  rw_trx_hash_element_t() : trx(nullptr) {
+    mutex_create(LATCH_ID_RW_TRX_HASH_ELEMENT, &mutex);
+  }
+
+  ~rw_trx_hash_element_t() { mutex_free(&mutex); }
+
+  /* lf_hash_init() relies on this to be first in the struct. */
+  trx_id_t id = 0;
+
+  std::atomic<trx_id_t> no;
+
+  trx_t *trx;
+
+  ib_mutex_t mutex;
+};
+
+/** Wrapper around LF_HASH to store set of in-memory read-write transactions. */
+class rw_trx_hash_t {
+  LF_HASH hash;
+
+  /** Constructor callback for lock-free allocator.
+
+  Object is just allocated and is not yet accessible via rw_trx_hash by
+  concurrent threads. Object can be reused multiple times before it is freed.
+  Every time object is being reused initialize() callback is called. */
+  static void rw_trx_hash_constructor(uchar *arg);
+
+  /** Destructor callback for lock-free allocator.
+
+  Object is about to be freed and is not accessible via rw_trx_hash by
+  concurrent threads. */
+  static void rw_trx_hash_destructor(uchar *arg);
+
+  /** Destructor callback for lock-free allocator.
+
+  This destructor is used at shutdown. It frees remaining transaction objects.
+
+  XA PREPARED transactions may remain if they haven't been committed or rolled
+  back. ACTIVE transactions may remain if startup was interrupted or server is
+  running in read-only mode or for certain srv_force_recovery levels. */
+  static void rw_trx_hash_shutdown_destructor(uchar *arg);
+
+  /** Initializer callback for lock-free hash.
+
+  Object is not yet accessible via rw_trx_hash by concurrent threads, but is
+  about to become such. Object id can be changed only by this callback and
+  remains the same until all pins to this object are released.
+
+  Object trx can be changed to 0 by erase() under object mutex protection,
+  which indicates it is about to be removed from lock-free hash and become not
+  accessible by concurrent threads. */
+  static void rw_trx_hash_initialize(rw_trx_hash_element_t *element,
+                                     trx_t *trx);
+
+  /** Gets LF_HASH pins.
+
+  Pins are used to protect object from being destroyed or reused. They are
+  normally stored in trx object for quick access. If caller doesn't have trx
+  available, we try to get it using current_trx(). If caller doesn't have trx at
+  all, temporary pins are allocated. */
+  LF_PINS *get_pins(trx_t *trx);
+
+  struct eliminate_duplicates_arg {
+    trx_ids_t ids;
+    lf_hash_walk_func *action;
+    void *argument;
+
+    eliminate_duplicates_arg(size_t size, lf_hash_walk_func *act, void *arg)
+        : action(act), argument(arg) {
+      ids.reserve(size);
+    }
+  };
+
+  static bool eliminate_duplicates(rw_trx_hash_element_t *element,
+                                   eliminate_duplicates_arg *arg);
+
+#ifdef UNIV_DEBUG
+  static void validate_element(trx_t *trx);
+
+  struct debug_iterator_arg {
+    lf_hash_walk_func *action;
+    void *argument;
+  };
+
+  static bool debug_iterator(rw_trx_hash_element_t *element,
+                             debug_iterator_arg *arg);
+#endif /* UNIV_DEBUG */
+
+ public:
+  void init();
+
+  void destroy();
+
+  /** Releases LF_HASH pins.
+
+  Must be called by thread that owns trx_t object when the later is being
+  "detached" from thread (e.g. released to the pool by trx_free()). Can be
+  called earlier if thread is expected not to use rw_trx_hash.
+
+  Since pins are not allowed to be transferred to another thread,
+  initialisation thread calls this for recovered transactions. */
+  void put_pins(trx_t *trx);
+
+  /** Finds trx object in lock-free hash with given id.
+
+  Only ACTIVE or PREPARED trx objects may participate in hash. Nevertheless the
+  transaction may get committed before this method returns.
+
+  With do_ref_count == false the caller may dereference returned trx pointer
+  only if lock_sys.mutex was acquired before calling find().
+
+  With do_ref_count == true caller dereferemce trx even if it is not holding
+  lock_sys.mutex. Caller is responsible for calling trx->release_reference()
+  when it is done playing with trx.
+
+  Ideally this method should get caller rw_trx_hash_pins along with trx object
+  as a parameter, similar to insert() and erase(). However most callers lose trx
+  early in their call chains and it is not that easy to pass them through.
+
+  So we take more expensive approach: get trx through current_thd()->ha_data.
+  Some threads don't have trx attached to THD, and at least server
+  initialisation thread, fts_optimize_thread, srv_master_thread,
+  dict_stats_thread, srv_monitor_thread, btr_defragment_thread don't even have
+  THD at all. For such cases we allocate pins only for duration of search and
+  free them immediately.
+
+  This has negative performance impact and should be fixed eventually (by
+  passing caller_trx as a parameter). Still stream of DML is more or less Ok.
+
+  @return pointer to trx or nullptr if not found */
+  trx_t *find(trx_t *caller_trx, trx_id_t trx_id, bool do_ref_count);
+
+  /** Inserts trx to lock-free hash.
+
+  Object becomes accessible via rw_trx_hash. */
+  void insert(trx_t *trx);
+
+  /** Removes trx from lock-free hash.
+
+  Object becomes not accessible via rw_trx_hash. But it still can be pinned by
+  concurrent find(), which is supposed to release it immediately after it sees
+  object trx is nullptr. */
+  void erase(trx_t *trx);
+
+  /** Returns the number of elements in the hash.
+
+  The number is exact only if hash is protected against concurrent modifications
+  (e.g., single threaded startup or hash is protected by some mutex). Otherwise
+  the number maybe used as a hint only, because it may change even before this
+  method returns. */
+  uint32_t size();
+
+  /** Iterates the hash.
+
+  @param caller_trx used to get/set pins
+  @param action     called for every element in hash
+  @param argument   opque argument passed to action
+
+  May return the same element multiple times if hash is under contention. If
+  caller doesn't like to see the same transaction multiple times, it has to call
+  iterate_no_dups() instead.
+
+  May return element with committed transaction. If caller doesn't like to see
+  committed transactions, it has to skip those under element mutex:
+
+    mutex_enter(&element->mutex);
+    trx_t *trx = element->trx;
+    if (trx != nullptr) {
+      // trx is protected against commit in this branch
+    }
+    mutex_exit(&element->mutex);
+
+  May miss concurrently inserted transactions.
+
+  @return 0 if iteration completed successfuly, or 1 if iteration was
+  interrupted (action returned true) */
+  int iterate(trx_t *caller_trx, const lf_hash_walk_func *action, const void *argument);
+
+  int iterate(const lf_hash_walk_func *action, const void *argument);
+
+  /** Iterates the hash and eliminates duplicate elements.
+
+  @sa iterate() */
+  int iterate_no_dups(trx_t *caller_trx, lf_hash_walk_func *action,
+                      void *argument);
+
+  int iterate_no_dups(lf_hash_walk_func *action, void *argument);
+};
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** The transaction system central memory data structure. */
+struct trx_sys_t {
+ private:
+  /** To avoid false sharing */
+  char pad1[INNOBASE_CACHE_LINE_SIZE];
+  /** The smallest number not yet assigned as a transaction id or transaction
+  number. Accessed and updated with atomic operations. */
+  std::atomic<trx_id_t> max_trx_id;
+
+  /** To avoid false sharing */
+  char pad2[INNOBASE_CACHE_LINE_SIZE];
+  /** Solves race conditions between register_rw() and snapshot_ids() as well as
+  race condition between assign_new_trx_no() and snapshot_ids().
+
+  @sa register_rw()
+  @sa assign_new_trx_no()
+  @sa snapshot_ids() */
+  std::atomic<trx_id_t> rw_trx_hash_version;
+
+ public:
+  /** To avoid false sharing */
+  char pad3[INNOBASE_CACHE_LINE_SIZE];
+  /** Mutex protecting trx list. */
+  mutable TrxSysMutex mutex;
+
+  /** To avoid false sharing */
+  char pad4[INNOBASE_CACHE_LINE_SIZE];
+  /** List of all transactions. */
+  trx_ut_list_t trx_list;
+
+  /** To avoid false sharing */
+  char pad5[INNOBASE_CACHE_LINE_SIZE];
+  /** Lock-free hash of in-memory read-write transactions. Works faster when
+  it's on it's own cache line (tested). */
+  rw_trx_hash_t rw_trx_hash;
+
+  char pad6[INNOBASE_CACHE_LINE_SIZE]; /*!< To avoid false sharing */
+
+  Rsegs rsegs; /*!< Vector of pointers to rollback
+               segments. These rsegs are iterated
+               and added to the end under a read
+               lock. They are deleted under a write
+               lock while the vector is adjusted.
+               They are created and destroyed in
+               single-threaded mode. */
+
+  Rsegs tmp_rsegs; /*!< Vector of pointers to rollback
+                   segments within the temp tablespace;
+                   This vector is created and destroyed
+                   in single-threaded mode so it is not
+                   protected by any mutex because it is
+                   read-only during multi-threaded
+                   operation. */
+
+  std::atomic<ulint> rseg_history_len;
+  /*!< Length of the TRX_RSEG_HISTORY
+  list (update undo logs for committed
+  transactions), protected by
+  rseg->mutex */
+
+  /** To avoid false sharing */
+  char pad7[INNOBASE_CACHE_LINE_SIZE];
+#ifdef UNIV_DEBUG
+  std::atomic<trx_id_t> rw_max_trx_no; /*!< Max trx number of read-write
+                          transactions added for purge. */
+#endif                                 /* UNIV_DEBUG */
+
+  /** Returns the minimum trx id in rw trx list.
+
+  This is the smallest id for which the trx can possibly be active. (But, you
+  must look at trx->state to find out if the minimum trx id transaction itself
+  is active, or already committed.
+
+  @return the minimum trx id, or max_trx_id if the trx list is empty */
+  trx_id_t get_min_trx_id() {
+    trx_id_t id = get_max_trx_id();
+    rw_trx_hash.iterate(
+        reinterpret_cast<lf_hash_walk_func *>(get_min_trx_id_callback), &id);
+    return id;
+  }
+
+  /** Determines the maximum transaction id.
+
+  @return maximum currently allocated trx id; will be stale after the next call
+  to trx_sys.assign_new_trx_no */
+  trx_id_t get_max_trx_id() {
+    return max_trx_id.load(std::memory_order_relaxed);
+  }
+
+  /** Allocates and assigns new transaction serialisation number.
+
+  There's a gap between max_trx_id increment and transaction serialisation
+  number becoming visible through rw_trx_hash. While we're in this gap
+  concurrent thread may come and do MVCC snapshot without seeing allocated but
+  not yet assigned serialisation number. Then at some point purge thread may
+  clone this view. As a result it won't see newly allocated serialisation number
+  and may remove "unnecessary" history data of this transaction from rollback
+  segments.
+
+  rw_trx_hash_version is intended to solve this problem. MVCC snapshot has to
+  wait until max_trx_id == rw_trx_hash_version, which effectively means that all
+  transaction serialisation numbers up to max_trx_id are available through
+  rw_trx_hash.
+
+  We rely on refresh_rw_trx_hash_version() to issue RELEASE memory barrier so
+  that rw_trx_hash_version increment happens after trx->rw_trx_hash_element->no
+  becomes available visible through rw_trx_hash.
+
+  @param trx transaction */
+  void assign_new_trx_no(trx_t *trx) {
+    trx->no = get_new_trx_id_no_refresh();
+    trx->rw_trx_hash_element->no.store(trx->no, std::memory_order_relaxed);
+    refresh_rw_trx_hash_version();
+  }
+
+  /** Takes MVCC snapshot.
+
+  To reduce malloc probability we reserve rw_trx_hash.size() + 32 elements in
+  ids.
+
+  For details about get_rw_trx_hash_version() != get_max_trx_id() spin
+  @sa register_rw() and @sa assign_new_trx_no().
+
+  We rely on get_rw_trx_hash_version() to issue ACQUIRE memory barrier so that
+  loading of rw_trx_hash_version happens before accessing rw_trx_hash.
+
+  To optimise snapshot creation rw_trx_hash.iterate is being used instead of
+  rw_trx_hash.iterate_no_dups(). It means that some transaction identifiers may
+  appear multiple times in ids.
+
+  @param[in,out] caller_trx used to get access to rw_trx_hash_pins
+  @param[out]    ids        array to store registered transaction identifiers
+  @param[out]    max_trx_id variable to store max_trx_id value
+  @param[out]    mix_trx_no variable to store min(trx->no) value */
+  void snapshot_ids(trx_t *caller_trx, trx_ids_t *ids, trx_id_t *max_trx_id,
+                    trx_id_t *min_trx_no) {
+    ut_ad(!mutex_own(&mutex));
+    snapshot_ids_arg arg(ids);
+
+    while ((arg.id = get_rw_trx_hash_version()) != get_max_trx_id()) {
+      ut_delay(1);
+    }
+    arg.no = arg.id;
+
+    uint32_t hashSize = rw_trx_hash.size();
+    ids->clear();
+
+    if (hashSize != 0) {
+        ids->reserve(hashSize + 32);
+        rw_trx_hash.iterate(
+            caller_trx, reinterpret_cast<lf_hash_walk_func *>(copy_one_id), &arg);
+    }
+
+    *max_trx_id = arg.id;
+    *min_trx_no = arg.no;
+  }
+
+  /** Initialiser for max_trx_id and rw_trx_hash_version. */
+  void init_max_trx_id(trx_id_t value) {
+    max_trx_id = rw_trx_hash_version = value;
+  }
+
+  /** @return total number of active (non-prepared) transactions */
+  ulint any_active_transactions();
+
+  /** Registers read-write transaction.
+
+  Transaction becomes visible to MVCC.
+
+  There's a gap between max_trx_id increment and transaction becoming visible
+  through rw_trx_hash. While we're in this gap concurrent thread may come and do
+  MVCC snapshot. As a result concurrent readview will be able to observe records
+  owned by this transaction even before it is committed.
+
+  rw_trx_hash_version is intendded to solve this problem. MVCC snapshot has to
+  wait until max_trx_id == rw_trx_hash_version, which effectively means that all
+  transactions up to max_trx_id are available through rw_trx_hash.
+
+  We rely on refresh_rw_trx_hash_version() to issue RELEASE memory barrier so
+  that rw_trx_hash_version increment happens after transaction becomes visible
+  through rw_trx_hash. */
+  void register_rw(trx_t *trx) {
+    trx->id = get_new_trx_id_no_refresh();
+    rw_trx_hash.insert(trx);
+    refresh_rw_trx_hash_version();
+  }
+
+  /** For replica only, registers a faked read-write transaction. */
+  void register_rw_replica(trx_t *trx) {
+    /* trx->id and trx_sys->max_trx_id were already set. */
+    rw_trx_hash.insert(trx);
+    rw_trx_hash_version.store(get_max_trx_id(), std::memory_order_release);
+  }
+
+  /** Deregisters read-write transaction.
+
+  Transaction is removed from rw_trx_hash, which releases all implicit locks.
+  MVCC snapshot won't see this transaction anymore. */
+  void deregister_rw(trx_t *trx) { rw_trx_hash.erase(trx); }
+
+  bool is_registered(trx_t *caller_trx, trx_id_t id) {
+    return (id > 0) && (find(caller_trx, id, false) != nullptr);
+  }
+
+  trx_t *find(trx_t *caller_trx, trx_id_t id, bool do_ref_count = true) {
+    return rw_trx_hash.find(caller_trx, id, do_ref_count);
+  }
+
+  /** Registers transaction in trx_sys.
+
+  @param trx transaction */
+  void register_trx(trx_t *trx) {
+    mutex_enter(&mutex);
+    UT_LIST_ADD_FIRST(trx_list, trx);
+    mutex_exit(&mutex);
+  }
+
+  /** Deregisters transaction in trx_sys.
+
+  @param trx transaction */
+  void deregister_trx(trx_t *trx) {
+    mutex_enter(&mutex);
+    UT_LIST_REMOVE(trx_list, trx);
+    mutex_exit(&mutex);
+  }
+
+  /** Clones the oldest view and stores it in view.
+
+  No need to call ReadView::close(). The caller owns the view that is passed in.
+  This function is called by purge thread to determine whether it should purge
+  the delete marked record or not. */
+  void clone_oldest_view(ReadView *view = NULL);
+
+  /** @return the number of active views. */
+  size_t view_count() const {
+    size_t count = 0;
+
+    mutex_enter(&mutex);
+    for (const trx_t *trx = UT_LIST_GET_FIRST(trx_list); trx != nullptr;
+         trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+      if (trx->read_view.get_state() == READ_VIEW_STATE_OPEN) {
+        ++count;
+      }
+    }
+    mutex_exit(&mutex);
+    return count;
+  }
+
+  /** @return true if found prepared transaction(s). */
+  bool found_prepared_trx();
+
+ private:
+  static bool get_min_trx_id_callback(rw_trx_hash_element_t *element,
+                                      trx_id_t *id) {
+    if (element->id < *id) {
+      mutex_enter(&element->mutex);
+      /* We don't care about read-only transactions here. */
+      if (element->trx != nullptr &&
+          element->trx->rsegs.m_redo.rseg != nullptr) {
+        *id = element->id;
+      }
+      mutex_exit(&element->mutex);
+    }
+    return false;
+  }
+
+  struct snapshot_ids_arg {
+    snapshot_ids_arg(trx_ids_t *_ids) : ids(_ids), id(0), no(0) {}
+
+    trx_ids_t *ids;
+    trx_id_t id;
+    trx_id_t no;
+  };
+
+  static bool copy_one_id(rw_trx_hash_element_t *element,
+                          snapshot_ids_arg *arg) {
+    if (element->id < arg->id) {
+      trx_id_t no = element->no.load(std::memory_order_relaxed);
+      arg->ids->push_back(element->id);
+      if (no < arg->no) {
+        arg->no = no;
+      }
+    }
+    return false;
+  }
+
+  /** Get for rw_trx_hash_version, must issue ACQUIRE memory barrier. */
+  trx_id_t get_rw_trx_hash_version() {
+    return rw_trx_hash_version.load(std::memory_order_acquire);
+  }
+
+  /** Increments rw_trx_hash_version, must issue RELEASE memory barrier. */
+  void refresh_rw_trx_hash_version() {
+    rw_trx_hash_version.fetch_add(1, std::memory_order_release);
+  }
+
+  /** Allocates new transaction id without refreshing rw_trx_hash_version.
+
+  This method is extracted for exclusive use by register_rw() and
+  assign_new_trx_no() where new id must be allocated atomically with payload
+  of these methods from MVCC snapshot point of view.
+
+  @sa assign_new_trx_no()
+
+  @return new transaction id */
+  trx_id_t get_new_trx_id_no_refresh();
+};
+#else
 /** The transaction system central memory data structure. */
 struct trx_sys_t {
   TrxSysMutex mutex; /*!< mutex protecting most fields in
@@ -504,6 +1021,7 @@ struct trx_sys_t {
   bool found_prepared_trx; /*!< True if XA PREPARED trxs are
                            found. */
 };
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 #endif /* !UNIV_HOTBACKUP */
 
diff --git a/storage/innobase/include/trx0sys.ic b/storage/innobase/include/trx0sys.ic
index cb012de..db43007 100644
--- a/storage/innobase/include/trx0sys.ic
+++ b/storage/innobase/include/trx0sys.ic
@@ -179,6 +179,7 @@ trx_id_t trx_read_trx_id(
   return (mach_read_from_6(ptr));
 }
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** Looks for the trx handle with the given id in rw_trx_list.
  The caller must be holding trx_sys->mutex.
  @return the trx handle or NULL if not found;
@@ -380,4 +381,5 @@ void trx_sys_rw_trx_add(trx_t *trx) {
   ut_d(trx->in_rw_trx_list = true);
 }
 
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #endif /* !UNIV_HOTBACKUP */
diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h
index 9574424..38ed074 100644
--- a/storage/innobase/include/trx0trx.h
+++ b/storage/innobase/include/trx0trx.h
@@ -45,6 +45,9 @@ this program; if not, write to the Free Software Foundation, Inc.,
 #include "log0log.h"
 #include "mem0mem.h"
 #include "que0types.h"
+#ifdef HAVE_LOCK_FREE_READVIEW
+#include "read0types.h"
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #include "trx0xa.h"
 #include "usr0types.h"
 #include "ut0vec.h"
@@ -56,12 +59,22 @@ this program; if not, write to the Free Software Foundation, Inc.,
 // Forward declaration
 struct mtr_t;
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 // Forward declaration
 class ReadView;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 // Forward declaration
 class FlushObserver;
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+// Forward declaration
+struct rw_trx_hash_element_t;
+
+// Forward declaration
+struct LF_PINS;
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
 /** Dummy session used currently in MySQL interface */
 extern sess_t *trx_dummy_sess;
 
@@ -94,16 +107,27 @@ trx_t *trx_allocate_for_background(void);
 /** Resurrect table locks for resurrected transactions. */
 void trx_resurrect_locks();
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** Release a trx_t instance back to the pool.
+@param[in,out]  trx the instance to release */
+void trx_free(trx_t *&trx);
+#else
 /** Free and initialize a transaction object instantiated during recovery.
 @param[in,out]	trx	transaction object to free and initialize */
 void trx_free_resurrected(trx_t *trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Free a transaction that was allocated by background or user threads.
 @param[in,out]	trx	transaction object to free */
 void trx_free_for_background(trx_t *trx);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** At shutdown, frees a transaction object. */
+void trx_free_at_shutdown(trx_t *trx);
+#else
 /** At shutdown, frees a transaction object that is in the PREPARED state. */
 void trx_free_prepared(trx_t *trx); /*!< in, own: trx object */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Free a transaction object for MySQL.
 @param[in,out]	trx	transaction */
@@ -187,10 +211,12 @@ void trx_commit_low(
     trx_t *trx,  /*!< in/out: transaction */
     mtr_t *mtr); /*!< in/out: mini-transaction (will be committed),
                  or NULL if trx made no modifications */
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** Cleans up a transaction at database startup. The cleanup is needed if
  the transaction already got to the middle of a commit when the database
  crashed, and we cannot roll it back. */
 void trx_cleanup_at_db_startup(trx_t *trx); /*!< in: transaction */
+#endif                                      /* HAVE_LOCK_FREE_READVIEW */
 /** Does the transaction commit for MySQL.
  @return DB_SUCCESS or error number */
 dberr_t trx_commit_for_mysql(trx_t *trx); /*!< in/out: transaction */
@@ -220,10 +246,12 @@ trx_t *trx_get_trx_by_xid(
 void trx_commit_complete_for_mysql(trx_t *trx); /*!< in/out: transaction */
 /** Marks the latest SQL statement ended. */
 void trx_mark_sql_stat_end(trx_t *trx); /*!< in: trx handle */
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** Assigns a read view for a consistent read query. All the consistent reads
  within the same transaction will get the same read view, which is created
  when this function is first called for a new started transaction. */
 ReadView *trx_assign_read_view(trx_t *trx); /*!< in: active transaction */
+#endif                                      /* HAVE_LOCK_FREE_READVIEW */
 
 /** @return the transaction's read view or NULL if one not assigned. */
 UNIV_INLINE
@@ -361,6 +389,7 @@ tagged as such.
 @param[in,out] trx	Transaction that needs to be "upgraded" to RW from RO */
 void trx_set_rw_mode(trx_t *trx);
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /**
 Increase the reference count. If the transaction is in state
 TRX_STATE_COMMITTED_IN_MEMORY then the transaction is considered
@@ -380,6 +409,7 @@ void trx_release_reference(trx_t *trx);
 /**
 Check if the transaction is being referenced. */
 #define trx_is_referenced(t) ((t)->n_ref > 0)
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /**
 @param[in] requestor	Transaction requesting the lock
@@ -442,6 +472,7 @@ with an explicit check for the read-only status.
 #define trx_is_ac_nl_ro(t) \
   ((t)->read_only && trx_is_autocommit_non_locking((t)))
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /**
 Assert that the transaction is in the trx_sys_t::rw_trx_list */
 #define assert_trx_in_rw_list(t)                         \
@@ -451,6 +482,7 @@ Assert that the transaction is in the trx_sys_t::rw_trx_list */
           !((t)->read_only || !(t)->rsegs.m_redo.rseg)); \
     check_trx_state(t);                                  \
   } while (0)
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /**
 Check transaction state */
@@ -472,6 +504,18 @@ Check transaction state */
 
 /** Check if transaction is free so that it can be re-initialized.
 @param t transaction handle */
+#ifdef HAVE_LOCK_FREE_READVIEW
+#define assert_trx_is_free(t)                            \
+  do {                                                   \
+    ut_ad(trx_state_eq((t), TRX_STATE_NOT_STARTED) ||    \
+          trx_state_eq((t), TRX_STATE_FORCED_ROLLBACK)); \
+    ut_ad(!trx_is_rseg_updated(trx));                    \
+    ut_ad(!(t)->read_view.is_open());                    \
+    ut_ad((t)->lock.wait_thr == nullptr);                \
+    ut_ad(UT_LIST_GET_LEN((t)->lock.trx_locks) == 0);    \
+    ut_ad((t)->dict_operation == TRX_DICT_OP_NONE);      \
+  } while (0)
+#else
 #define assert_trx_is_free(t)                            \
   do {                                                   \
     ut_ad(trx_state_eq((t), TRX_STATE_NOT_STARTED) ||    \
@@ -482,6 +526,7 @@ Check transaction state */
     ut_ad(UT_LIST_GET_LEN((t)->lock.trx_locks) == 0);    \
     ut_ad((t)->dict_operation == TRX_DICT_OP_NONE);      \
   } while (0)
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Check if transaction is in-active so that it can be freed and put back to
 transaction pool.
@@ -493,6 +538,25 @@ transaction pool.
   } while (0)
 
 #ifdef UNIV_DEBUG
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** Assert that an autocommit non-locking select cannot be in the
+ rw_trx_hash and that it is a read-only transaction.
+ The tranasction must have mysql_thd assigned. */
+#define assert_trx_nonlocking_or_in_list(t)         \
+  do {                                              \
+    if (trx_is_autocommit_non_locking(t)) {         \
+      trx_state_t t_state = (t)->state;             \
+      ut_ad((t)->read_only);                        \
+      ut_ad(!(t)->is_recovered);                    \
+      ut_ad((t)->mysql_thd != nullptr);             \
+      ut_ad(t_state == TRX_STATE_NOT_STARTED ||     \
+            t_state == TRX_STATE_FORCED_ROLLBACK || \
+            t_state == TRX_STATE_ACTIVE);           \
+    } else {                                        \
+      check_trx_state(t);                           \
+    }                                               \
+  } while (0)
+#else
 /** Assert that an autocommit non-locking select cannot be in the
  rw_trx_list and that it is a read-only transaction.
  The tranasction must be in the mysql_trx_list. */
@@ -511,10 +575,17 @@ transaction pool.
       check_trx_state(t);                           \
     }                                               \
   } while (0)
-#else /* UNIV_DEBUG */
+#endif /* HAVE_LOCK_FREE_READVIEW */
+#else  /* UNIV_DEBUG */
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** Assert that an autocommit non-locking select cannot be in the
+ rw_trx_hash and that it is a read-only transaction.
+ The tranasction must have mysql_thd assigned. */
+#else
 /** Assert that an autocommit non-locking slect cannot be in the
  rw_trx_list and that it is a read-only transaction.
  The tranasction must be in the mysql_trx_list. */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #define assert_trx_nonlocking_or_in_list(trx) ((void)0)
 #endif /* UNIV_DEBUG */
 #endif /* !UNIV_HOTBACKUP */
@@ -778,6 +849,18 @@ enum trx_rseg_type_t {
 };
 
 struct trx_t {
+#ifdef HAVE_LOCK_FREE_READVIEW
+ private:
+  std::atomic<int32_t> n_ref; /*!< Count of references, protected
+              by trx_t::mutex. We can't release the
+              locks nor commit the transaction until
+              this reference is 0.  We can change
+              the state to COMMITTED_IN_MEMORY to
+              signify that it is no longer
+              "active". */
+
+ public:
+#endif /* HAVE_LOCK_FREE_READVIEW */
   enum isolation_level_t {
 
     /** dirty read: non-locking SELECTs are performed so that we
@@ -828,6 +911,15 @@ struct trx_t {
 
   trx_id_t id; /*!< transaction id */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_id_t no; /*!< transaction serialization number:
+               max trx id shortly before the
+               transaction is moved to
+               COMMITTED_IN_MEMORY state.
+               Protected by trx_sys_t::mutex
+               when trx is in rw_trx_hash. Initially
+               set to TRX_ID_MAX. */
+#else
   trx_id_t no; /*!< transaction serialization number:
                max trx id shortly before the
                transaction is moved to
@@ -835,7 +927,73 @@ struct trx_t {
                Protected by trx_sys_t::mutex
                when trx->in_rw_trx_list. Initially
                set to TRX_ID_MAX. */
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
+#ifdef HAVE_LOCK_FREE_READVIEW
+  /** State of the trx from the point of view of concurrency control
+  and the valid state transitions.
+
+  Possible states:
+
+  TRX_STATE_NOT_STARTED
+  TRX_STATE_FORCED_ROLLBACK
+  TRX_STATE_ACTIVE
+  TRX_STATE_PREPARED
+  TRX_STATE_COMMITTED_IN_MEMORY (alias below COMMITTED)
+
+  Valid state transitions are:
+
+  Regular transactions:
+  * NOT_STARTED -> ACTIVE -> COMMITTED -> NOT_STARTED
+
+  Auto-commit non-locking read-only:
+  * NOT_STARTED -> ACTIVE -> NOT_STARTED
+
+  XA (2PC):
+  * NOT_STARTED -> ACTIVE -> PREPARED -> COMMITTED -> NOT_STARTED
+
+  Recovered XA:
+  * NOT_STARTED -> PREPARED -> COMMITTED -> (freed)
+
+  XA (2PC) (shutdown or disconnect before ROLLBACK or COMMIT):
+  * NOT_STARTED -> PREPARED -> (freed)
+
+  Disconnected XA can become recovered:
+  * ... -> ACTIVE -> PREPARED (connected) -> PREPARED (disconnected)
+  Disconnected means from mysql e.g due to the mysql client disconnection.
+  Latching and various transaction lists membership rules:
+
+  XA (2PC) transactions are always treated as non-autocommit.
+
+  Transitions to ACTIVE or NOT_STARTED occur when transaction
+  is not in rw_trx_hash (no trx_sys->mutex needed).
+
+  Autocommit non-locking read-only transactions move between states
+  without holding any mutex. They are not in rw_trx_hash.
+
+  All transactions, unless they are determined to be ac-nl-ro,
+  explicitly tagged as read-only or read-write, will first be put
+  on the read-only transaction list. Only when a !read-only transaction
+  in the read-only list tries to acquire an X or IX lock on a table
+  do we remove it from the read-only list and put it on the read-write
+  list. During this switch we assign it a rollback segment.
 
+  When a transaction is NOT_STARTED, it can be in trx_list. It cannot be
+  in rw_trx_hash.
+
+  ACTIVE->PREPARED->COMMITTED is only possible when trx is in rw_trx_hash.
+  The transition ACTIVE->PREPARED is protected by trx_sys->mutex.
+
+  ACTIVE->COMMITTED is possible when the transaction is in
+  rw_trx_hash.
+
+  Transitions to COMMITTED are protected by both lock_sys->mutex
+  and trx->mutex.
+
+  NOTE: Some of these state change constraints are an overkill,
+  currently only required for a consistent view for printing stats.
+  This unnecessarily adds a huge cost for the general case. */
+#else
   /** State of the trx from the point of view of concurrency control
   and the valid state transitions.
 
@@ -898,6 +1056,7 @@ struct trx_t {
   NOTE: Some of these state change constraints are an overkill,
   currently only required for a consistent view for printing stats.
   This unnecessarily adds a huge cost for the general case. */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   trx_state_t state;
 
@@ -908,6 +1067,14 @@ struct trx_t {
   concurrent unique insert or replace operation. */
   bool skip_lock_inheritance;
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ReadView read_view; /*!< consistent read view used in the
+                      transaction, or NULL if not yet set */
+
+  UT_LIST_NODE_T(trx_t)
+  trx_list; /*!< list of all transactions;
+            protected by trx_sys->mutex. */
+#else
   ReadView *read_view; /*!< consistent read view used in the
                        transaction, or NULL if not yet set */
 
@@ -918,15 +1085,23 @@ struct trx_t {
   no_list; /*!< Required during view creation
            to check for the view limit for
            transactions that are committing */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   trx_lock_t lock;   /*!< Information about the transaction
                      locks and state. Protected by
                      trx->mutex or lock_sys->mutex
                      or both */
+#ifdef HAVE_LOCK_FREE_READVIEW
+  bool is_recovered; /*!< 0=normal transaction,
+                     1=recovered, must be rolled back,
+                     protected by trx_sys->mutex when
+                     trx is in rw_trx_hash */
+#else
   bool is_recovered; /*!< 0=normal transaction,
                      1=recovered, must be rolled back,
                      protected by trx_sys->mutex when
                      trx->in_rw_trx_list holds */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   os_thread_id_t killed_by; /*!< The thread ID that wants to
                             kill this transaction asynchronously.
@@ -1045,6 +1220,7 @@ struct trx_t {
   statement uses, except those
   in consistent read */
   /*------------------------------*/
+#ifndef HAVE_LOCK_FREE_READVIEW
 #ifdef UNIV_DEBUG
   /** The following two fields are mutually exclusive. */
   /* @{ */
@@ -1060,6 +1236,7 @@ struct trx_t {
   /*!< true if in
   trx_sys->mysql_trx_list */
 #endif /* UNIV_DEBUG */
+#endif /* HAVE_LOCK_FREE_READVIEW */
   /*------------------------------*/
   dberr_t error_state;             /*!< 0 if no error, otherwise error
                                    number; NOTE That ONLY the thread
@@ -1155,6 +1332,7 @@ struct trx_t {
   const char *start_file; /*!< Filename where it was started */
 #endif                    /* UNIV_DEBUG */
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   lint n_ref; /*!< Count of references, protected
               by trx_t::mutex. We can't release the
               locks nor commit the transaction until
@@ -1162,6 +1340,7 @@ struct trx_t {
               the state to COMMITTED_IN_MEMORY to
               signify that it is no longer
               "active". */
+#endif        /* HAVE_LOCK_FREE_READVIEW */
 
   /** Version of this instance. It is incremented each time the
   instance is re-used in trx_start_low(). It is used to track
@@ -1190,6 +1369,10 @@ struct trx_t {
                   doing Non-locking Read-only Read
                   Committed on DD tables */
 #endif            /* UNIV_DEBUG */
+#ifdef HAVE_LOCK_FREE_READVIEW
+  rw_trx_hash_element_t *rw_trx_hash_element{nullptr};
+  LF_PINS *rw_trx_hash_pins{nullptr};
+#endif /* HAVE_LOCK_FREE_READVIEW */
   ulint magic_n;
 
   bool is_read_uncommitted() const {
@@ -1210,6 +1393,20 @@ struct trx_t {
   }
 
   bool allow_semi_consistent() const { return (skip_gap_locks()); }
+
+#ifdef HAVE_LOCK_FREE_READVIEW
+  bool is_referenced() { return (n_ref.load(std::memory_order_relaxed) > 0); }
+
+  void reference() {
+    int32_t old_n_ref = n_ref.fetch_add(1, std::memory_order_relaxed);
+    ut_a(old_n_ref >= 0);
+  }
+
+  void release_reference() {
+    int32_t old_n_ref = n_ref.fetch_sub(1, std::memory_order_relaxed);
+    ut_a(old_n_ref > 0);
+  }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 };
 #ifndef UNIV_HOTBACKUP
 
diff --git a/storage/innobase/include/trx0trx.ic b/storage/innobase/include/trx0trx.ic
index f33f2d5..cf4bb2a 100644
--- a/storage/innobase/include/trx0trx.ic
+++ b/storage/innobase/include/trx0trx.ic
@@ -32,11 +32,19 @@ this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "read0read.h"
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** Determines if a transaction is in the given state.
+ The caller must hold trx_sys->mutex, or it must be the thread
+ that is serving a running transaction.
+ A running RW transaction must be in trx_sys->rw_trx_hash.
+ @return true if trx->state == state */
+#else
 /** Determines if a transaction is in the given state.
  The caller must hold trx_sys->mutex, or it must be the thread
  that is serving a running transaction.
  A running RW transaction must be in trx_sys->rw_trx_list.
  @return true if trx->state == state */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 UNIV_INLINE
 bool trx_state_eq(const trx_t *trx,  /*!< in: transaction */
                   trx_state_t state) /*!< in: state */
@@ -65,7 +73,9 @@ bool trx_state_eq(const trx_t *trx,  /*!< in: transaction */
       ut_a(state == TRX_STATE_NOT_STARTED ||
            state == TRX_STATE_FORCED_ROLLBACK);
 
+#ifndef HAVE_LOCK_FREE_READVIEW
       ut_ad(!trx->in_rw_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
       return (true);
   }
@@ -216,6 +226,7 @@ bool trx_is_rseg_assigned(const trx_t *trx) /*!< in: transaction */
   return (trx->rsegs.m_redo.rseg != NULL || trx->rsegs.m_noredo.rseg != NULL);
 }
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /**
 Increase the reference count. If the transaction is in state
 TRX_STATE_COMMITTED_IN_MEMORY then the transaction is considered
@@ -253,13 +264,18 @@ void trx_release_reference(trx_t *trx) {
 
   trx_mutex_exit(trx);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /**
 @param trx		Get the active view for this transaction, if one exists
 @return the transaction's read view or NULL if one not assigned. */
 UNIV_INLINE
 ReadView *trx_get_read_view(trx_t *trx) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+  return (!trx->read_view.is_open() ? nullptr : &trx->read_view);
+#else
   return (!MVCC::is_view_active(trx->read_view) ? NULL : trx->read_view);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 }
 
 /**
@@ -267,7 +283,11 @@ ReadView *trx_get_read_view(trx_t *trx) {
 @return the transaction's read view or NULL if one not assigned. */
 UNIV_INLINE
 const ReadView *trx_get_read_view(const trx_t *trx) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+  return (!trx->read_view.is_open() ? nullptr : &trx->read_view);
+#else
   return (!MVCC::is_view_active(trx->read_view) ? NULL : trx->read_view);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 }
 
 /**
diff --git a/storage/innobase/include/trx0types.h b/storage/innobase/include/trx0types.h
index a8f995b..08258c9 100644
--- a/storage/innobase/include/trx0types.h
+++ b/storage/innobase/include/trx0types.h
@@ -537,6 +537,7 @@ typedef std::priority_queue<
 
 typedef std::vector<trx_id_t, ut_allocator<trx_id_t>> trx_ids_t;
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** Mapping read-write transactions from id to transaction instance, for
 creating read views and during trx id lookup for MVCC and locking. */
 struct TrxTrack {
@@ -570,6 +571,7 @@ struct TrxTrackCmp {
 
 // typedef std::unordered_set<TrxTrack, TrxTrackHash, TrxTrackHashCmp> TrxIdSet;
 typedef std::set<TrxTrack, TrxTrackCmp, ut_allocator<TrxTrack>> TrxIdSet;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 struct TrxVersion {
   TrxVersion(trx_t *trx);
diff --git a/storage/innobase/include/trx0undo.h b/storage/innobase/include/trx0undo.h
index ad42905..345d813 100644
--- a/storage/innobase/include/trx0undo.h
+++ b/storage/innobase/include/trx0undo.h
@@ -272,9 +272,14 @@ the data can be discarded.
 @param[in]	noredo		whether the undo tablespace is redo logged */
 void trx_undo_insert_cleanup(trx_undo_ptr_t *undo_ptr, bool noredo);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** At shutdown, frees the undo logs of a transaction. */
+void trx_undo_free_at_shutdown(trx_t *trx);
+#else
 /** At shutdown, frees the undo logs of a PREPARED transaction. */
 void trx_undo_free_prepared(trx_t *trx) /*!< in/out: PREPARED transaction */
     UNIV_COLD;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /* Forward declaration. */
 namespace undo {
diff --git a/storage/innobase/include/ut0counter.h b/storage/innobase/include/ut0counter.h
index 80db26f..0abec07 100644
--- a/storage/innobase/include/ut0counter.h
+++ b/storage/innobase/include/ut0counter.h
@@ -46,7 +46,7 @@ this program; if not, write to the Free Software Foundation, Inc.,
 #include <functional>
 
 /** CPU cache line size */
-#ifdef __powerpc__
+#if (defined(__powerpc__)) || (defined(__aarch64__))
 #define INNOBASE_CACHE_LINE_SIZE 128
 #else
 #define INNOBASE_CACHE_LINE_SIZE 64
diff --git a/storage/innobase/include/ut0new.h b/storage/innobase/include/ut0new.h
index 8d10abf..8085d64 100644
--- a/storage/innobase/include/ut0new.h
+++ b/storage/innobase/include/ut0new.h
@@ -182,7 +182,9 @@ extern PSI_memory_key mem_key_partitioning;
 extern PSI_memory_key mem_key_row_log_buf;
 extern PSI_memory_key mem_key_row_merge_sort;
 extern PSI_memory_key mem_key_std;
+#ifndef HAVE_LOCK_FREE_READVIEW
 extern PSI_memory_key mem_key_trx_sys_t_rw_trx_ids;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 extern PSI_memory_key mem_key_undo_spaces;
 extern PSI_memory_key mem_key_ut_lock_free_hash_t;
 /* Please obey alphabetical order in the definitions above. */
diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc
index 5e6cfe3..d1cdfd4 100644
--- a/storage/innobase/lock/lock0lock.cc
+++ b/storage/innobase/lock/lock0lock.cc
@@ -206,7 +206,11 @@ bool lock_check_trx_id_sanity(
 {
   ut_ad(rec_offs_validate(rec, index, offsets));
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_id_t max_trx_id = trx_sys->get_max_trx_id();
+#else
   trx_id_t max_trx_id = trx_sys_get_max_trx_id();
+#endif /* HAVE_LOCK_FREE_READVIEW */
   bool is_ok = trx_id < max_trx_id;
 
   if (!is_ok) {
@@ -817,6 +821,9 @@ static const lock_t *lock_rec_other_has_conflicting(
  negatives. The caller must confirm all positive results by calling
  trx_is_active(). */
 static trx_t *lock_sec_rec_some_has_impl(
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_t *caller_trx,    /*!< in/out: trx of current thread */
+#endif                    /* HAVE_LOCK_FREE_READVIEW */
     const rec_t *rec,     /*!< in: user record */
     dict_index_t *index,  /*!< in: secondary index */
     const ulint *offsets) /*!< in: rec_get_offsets(rec, index) */
@@ -826,7 +833,9 @@ static trx_t *lock_sec_rec_some_has_impl(
   const page_t *page = page_align(rec);
 
   ut_ad(!lock_mutex_own());
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(!trx_sys_mutex_own());
+#endif /* HAVE_LOCK_FREE_READVIEW */
   ut_ad(!index->is_clustered());
   ut_ad(page_rec_is_user_rec(rec));
   ut_ad(rec_offs_validate(rec, index, offsets));
@@ -839,6 +848,21 @@ static trx_t *lock_sec_rec_some_has_impl(
   max trx id to the log, and therefore during recovery, this value
   for a page may be incorrect. */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (max_trx_id < trx_sys->get_min_trx_id()) {
+    trx = nullptr;
+
+  } else if (!lock_check_trx_id_sanity(max_trx_id, rec, index, offsets)) {
+    /* The page is corrupt: try to avoid a crash by returning 0 */
+    trx = nullptr;
+
+    /* In this case it is possible that some transaction has an implicit
+    x-lock. We have to look in the clustered index. */
+
+  } else {
+    trx = row_vers_impl_x_locked(caller_trx, rec, index, offsets);
+  }
+#else
   if (max_trx_id < trx_rw_min_trx_id() && !recv_recovery_is_on()) {
     trx = 0;
 
@@ -852,11 +876,60 @@ static trx_t *lock_sec_rec_some_has_impl(
   } else {
     trx = row_vers_impl_x_locked(rec, index, offsets);
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   return (trx);
 }
 
 #ifdef UNIV_DEBUG
+#ifdef HAVE_LOCK_FREE_READVIEW
+struct lock_rec_other_trx_holds_expl_arg {
+  ulint precise_mode;
+  const buf_block_t *const block;
+  ulint heap_no;
+  const trx_t *impl_trx;
+};
+
+static bool lock_rec_other_trx_holds_expl_callback(
+    rw_trx_hash_element_t *element, lock_rec_other_trx_holds_expl_arg *arg) {
+  mutex_enter(&element->mutex);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    const lock_t *expl_lock = lock_rec_has_expl(arg->precise_mode, arg->block,
+                                                arg->heap_no, arg->impl_trx);
+
+    ut_ad(!expl_lock || expl_lock->trx == arg->impl_trx);
+  }
+  mutex_exit(&element->mutex);
+  return false;
+}
+
+/** Checks if some transaction, other than given trx_id, has an explicit
+ lock on the given rec, in the given precise_mode. */
+static void lock_rec_other_trx_holds_expl(
+    trx_t *caller_trx,        /*!< in: current transaction */
+    ulint precise_mode,       /*!< in: LOCK_S or LOCK_X
+                              possibly ORed to LOCK_GAP or
+                              LOCK_REC_NOT_GAP. */
+    trx_t *trx,               /*!< in: trx holding implicit
+                              lock on rec */
+    const rec_t *rec,         /*!< in: user record */
+    const buf_block_t *block) /*!< in: buffer block
+                              containing the record */
+{
+  lock_mutex_enter();
+
+  ulint heap_no = page_rec_get_heap_no(rec);
+  lock_rec_other_trx_holds_expl_arg arg = {precise_mode, block, heap_no, trx};
+
+  trx_sys->rw_trx_hash.iterate(caller_trx,
+                               reinterpret_cast<lf_hash_walk_func *>(
+                                   lock_rec_other_trx_holds_expl_callback),
+                               &arg);
+
+  lock_mutex_exit();
+}
+#else
 /** Checks if some transaction, other than given trx_id, has an explicit
  lock on the given rec, in the given precise_mode.
 @param[in]   precise_mode   LOCK_S or LOCK_X possibly ORed to LOCK_GAP or
@@ -904,6 +977,7 @@ static bool lock_rec_other_trx_holds_expl(ulint precise_mode, const trx_t *trx,
 
   return (holds);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #endif /* UNIV_DEBUG */
 
 /** Return approximate number or record locks (bits set in the bitmap) for
@@ -4382,6 +4456,83 @@ static void lock_remove_all_on_table_for_trx(
   trx_mutex_exit(trx);
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+struct lock_remove_recovered_trx_record_locks_arg {
+  dict_table_t *table;
+  ulint n_recovered_trx;
+};
+
+static bool lock_remove_recovered_trx_record_locks_callback(
+    rw_trx_hash_element_t *element,
+    lock_remove_recovered_trx_record_locks_arg *arg) {
+  mutex_enter(&element->mutex);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    if (!trx->is_recovered) {
+      mutex_exit(&element->mutex);
+      return false;
+    }
+
+    /* Because we are holding the lock_sys->mutex,
+    implicit locks cannot be converted to explicit ones
+    while we are scanning the explicit locks. */
+
+    for (lock_t *lock = UT_LIST_GET_FIRST(trx->lock.trx_locks); lock != nullptr;
+         lock = UT_LIST_GET_NEXT(trx_locks, lock)) {
+      ut_a(lock->trx == trx);
+
+      /* Recovered transactions can't wait on a lock. */
+
+#ifdef HAVE_SCALABLE_LOCK_MGR
+      ut_a(!lock->is_waiting());
+#else
+      ut_a(!lock_get_wait(lock));
+#endif /* HAVE_SCALABLE_LOCK_MGR */
+
+      switch (lock_get_type_low(lock)) {
+        default:
+          ut_error;
+        case LOCK_TABLE:
+          if (lock->tab_lock.table == arg->table) {
+            lock_trx_table_locks_remove(lock);
+            lock_table_remove_low(lock);
+          }
+          break;
+        case LOCK_REC:
+          if (lock->index->table == arg->table) {
+            lock_rec_discard(lock);
+          }
+      }
+    }
+
+    ++arg->n_recovered_trx;
+  }
+  mutex_exit(&element->mutex);
+  return false;
+}
+
+/** Remove any explicit record locks held by recovering transactions on
+ the table.
+ @return number of recovered transactions examined */
+static ulint lock_remove_recovered_trx_record_locks(
+    dict_table_t *table) /*!< in: check if there are any locks
+                         held on records in this table or on the
+                         table itself */
+{
+  ut_a(table != nullptr);
+  ut_ad(lock_mutex_own());
+
+  lock_remove_recovered_trx_record_locks_arg arg = {table, 0};
+
+  trx_sys->rw_trx_hash.iterate(
+      reinterpret_cast<lf_hash_walk_func *>(
+          lock_remove_recovered_trx_record_locks_callback),
+      &arg);
+
+  return (arg.n_recovered_trx);
+}
+#else
+
 /** Remove any explicit record locks held by recovering transactions on
  the table.
  @return number of recovered transactions examined */
@@ -4452,6 +4603,7 @@ static ulint lock_remove_recovered_trx_record_locks(
 
   return (n_recovered_trx);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Removes locks on a table to be dropped.
  If remove_also_table_sx_locks is true then table-level S and X locks are
@@ -4709,7 +4861,11 @@ bool lock_print_info_summary(
       "------------\n",
       file);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  fprintf(file, "Trx id counter " TRX_ID_FMT "\n", trx_sys->get_max_trx_id());
+#else
   fprintf(file, "Trx id counter " TRX_ID_FMT "\n", trx_sys_get_max_trx_id());
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   fprintf(file,
           "Purge done for trx's n:o < " TRX_ID_FMT " undo n:o < " TRX_ID_FMT
@@ -4766,7 +4922,9 @@ struct PrintNotStarted {
   void operator()(const trx_t *trx) {
     /* We require exclusive access to lock_sys */
     ut_ad(lock_mutex_own());
+#ifndef HAVE_LOCK_FREE_READVIEW
     ut_ad(trx->in_mysql_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
     ut_ad(mutex_own(&trx_sys->mutex));
 
     /* See state transitions and locking rules in trx0trx.h */
@@ -4818,6 +4976,7 @@ class TrxLockIterator {
   ulint m_index;
 };
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** This iterates over both the RW and RO trx_sys lists. We need to keep
 track where the iterator was up to and we do that using an ordinal value. */
 
@@ -4875,6 +5034,7 @@ class TrxListIterator {
   /** For iterating over a transaction's locks */
   TrxLockIterator m_lock_iter;
 };
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Prints transaction lock wait and MVCC state.
 @param[in,out]	file	file where to print
@@ -4887,11 +5047,22 @@ void lock_trx_print_wait_and_mvcc_state(FILE *file, const trx_t *trx) {
 
   trx_print_latched(file, trx, 600);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  /* Note: read_view->get_state() check is race condition. But it
+  should "kind of work" because read_view is freed only at shutdown.
+  Worst thing that may happen is that it'll get transferred to
+  another thread and print wrong values. */
+
+  if (READ_VIEW_STATE_OPEN == trx->read_view.get_state()) {
+    trx->read_view.print_limits(file);
+  }
+#else
   const ReadView *read_view = trx_get_read_view(trx);
 
   if (read_view != NULL) {
     read_view->print_limits(file);
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   if (trx->lock.que_state == TRX_QUE_LOCK_WAIT) {
     fprintf(file,
@@ -4909,6 +5080,65 @@ void lock_trx_print_wait_and_mvcc_state(FILE *file, const trx_t *trx) {
   }
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** Prints info of locks for a transaction.
+ @return true if all printed, false if latches were released. */
+static void lock_trx_print_locks(
+    FILE *file,       /*!< in/out: File to write */
+    const trx_t *trx) /*!< in: current transaction */
+{
+  const lock_t *lock;
+  uint32_t i = 0;
+
+  /* Iterate over the transaction's locks. */
+  for (lock = UT_LIST_GET_FIRST(trx->lock.trx_locks); lock != nullptr;
+       lock = UT_LIST_GET_NEXT(trx_locks, lock)) {
+    if (lock_get_type_low(lock) == LOCK_REC) {
+      /* Print all the record locks on the page from
+      the record lock bitmap */
+
+      lock_rec_print(file, lock);
+    } else {
+      ut_ad(lock_get_type_low(lock) & LOCK_TABLE);
+
+      lock_table_print(file, lock);
+    }
+
+    if (++i == 10) {
+      fprintf(file,
+              "10 LOCKS PRINTED FOR THIS TRX:"
+              " SUPPRESSING FURTHER PRINTS\n");
+
+      break;
+    }
+  }
+}
+
+static bool lock_print_info_all_transactions_callback(
+    rw_trx_hash_element_t *element, FILE *file) {
+  mutex_enter(&element->mutex);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    check_trx_state(trx);
+
+    lock_trx_print_wait_and_mvcc_state(file, trx);
+
+    /* If we need to print the locked record contents then we
+    need to fetch the containing block from the buffer pool. */
+    if (srv_print_innodb_lock_monitor) {
+      /* Print the locks owned by the current transaction. */
+
+      trx->reference();
+      mutex_exit(&element->mutex);
+      lock_trx_print_locks(file, trx);
+      trx->release_reference();
+      return false;
+    }
+  }
+  mutex_exit(&element->mutex);
+  return false;
+}
+#else
 /** Prints info of locks for a transaction. This function will release the
  lock mutex and the trx_sys_t::mutex if the page was read from disk.
  @return true if page was read from the tablespace */
@@ -5019,6 +5249,7 @@ static bool lock_trx_print_locks(
 
   return (true);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Prints info of locks for each transaction. This function assumes that the
  caller holds the lock mutex and more importantly it will release the lock
@@ -5031,7 +5262,9 @@ void lock_print_info_all_transactions(
 
   fprintf(file, "LIST OF TRANSACTIONS FOR EACH SESSION:\n");
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   mutex_enter(&trx_sys->mutex);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /* First print info on non-active transactions */
 
@@ -5040,6 +5273,15 @@ void lock_print_info_all_transactions(
   available from INFORMATION_SCHEMA.INNODB_TRX. */
 
   PrintNotStarted print_not_started(file);
+#ifdef HAVE_LOCK_FREE_READVIEW
+  mutex_enter(&trx_sys->mutex);
+  ut_list_map(trx_sys->trx_list, print_not_started);
+  mutex_exit(&trx_sys->mutex);
+  trx_sys->rw_trx_hash.iterate(
+      reinterpret_cast<lf_hash_walk_func *>(
+          lock_print_info_all_transactions_callback),
+      file);
+#else
   ut_list_map(trx_sys->mysql_trx_list, print_not_started);
 
   const trx_t *trx;
@@ -5089,9 +5331,12 @@ void lock_print_info_all_transactions(
     a page from disk, or we didn't need to print the detail. */
     trx_iter.next();
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   lock_mutex_exit();
+#ifndef HAVE_LOCK_FREE_READVIEW
   mutex_exit(&trx_sys->mutex);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   ut_ad(lock_validate());
 }
@@ -5125,7 +5370,9 @@ static bool lock_table_queue_validate(
   const lock_t *lock;
 
   ut_ad(lock_mutex_own());
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(trx_sys_mutex_own());
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   for (lock = UT_LIST_GET_FIRST(table->locks); lock != NULL;
        lock = UT_LIST_GET_NEXT(tab_lock.locks, lock)) {
@@ -5209,14 +5456,22 @@ static bool lock_rec_queue_validate(
 
     trx_id = lock_clust_rec_some_has_impl(rec, index, offsets);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    const trx_t *impl_trx = nullptr;
+    impl_trx =
+        (trx_id > 0) ? trx_sys->find(current_trx(), trx_id, false) : nullptr;
+#else
     const trx_t *impl_trx = trx_rw_is_active_low(trx_id, NULL);
+#endif /* HAVE_LOCK_FREE_READVIEW */
     if (impl_trx != nullptr) {
       ut_ad(lock_mutex_own());
+#ifndef HAVE_LOCK_FREE_READVIEW
       ut_ad(trx_sys_mutex_own());
       /* impl_trx cannot become TRX_STATE_COMMITTED_IN_MEMORY nor removed from
       rw_trx_set until we release trx_sys->mutex, which means that currently all
       other threads in the system consider this impl_trx active and thus should
       respect implicit locks held by impl_trx*/
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
       const lock_t *other_lock =
           lock_rec_other_has_expl_req(LOCK_S, block, true, heap_no, impl_trx);
@@ -5347,6 +5602,38 @@ function_exit:
   return (true);
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+static bool lock_validate_table_locks_callback(rw_trx_hash_element_t *element,
+                                               void * /*unused*/) {
+  mutex_enter(&element->mutex);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    const lock_t *lock = nullptr;
+
+    check_trx_state(trx);
+
+    for (lock = UT_LIST_GET_FIRST(trx->lock.trx_locks); lock != nullptr;
+         lock = UT_LIST_GET_NEXT(trx_locks, lock)) {
+      if ((lock_get_type_low(lock) & LOCK_TABLE) != 0u) {
+        lock_table_queue_validate(lock->tab_lock.table);
+      }
+    }
+  }
+  mutex_exit(&element->mutex);
+  return false;
+}
+
+/** Validates the table locks.
+ @return true if ok */
+static bool lock_validate_table_locks() {
+  ut_ad(lock_mutex_own());
+  trx_sys->rw_trx_hash.iterate(
+      reinterpret_cast<lf_hash_walk_func *>(lock_validate_table_locks_callback),
+      nullptr);
+
+  return (true);
+}
+#else
 /** Validates the table locks.
  @return true if ok */
 static bool lock_validate_table_locks(
@@ -5376,6 +5663,7 @@ static bool lock_validate_table_locks(
 
   return (true);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Validate record locks up to a limit.
  @return lock at limit or NULL if no more locks in the hash bucket */
@@ -5386,7 +5674,9 @@ static MY_ATTRIBUTE((warn_unused_result)) const lock_t *lock_rec_validate(
                      (space, page_no) */
 {
   ut_ad(lock_mutex_own());
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(trx_sys_mutex_own());
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   for (const lock_t *lock = static_cast<const lock_t *>(
            HASH_GET_FIRST(lock_sys->rec_hash, start));
@@ -5447,9 +5737,13 @@ static bool lock_validate() {
   page_addr_set pages;
 
   lock_mutex_enter();
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_a(lock_validate_table_locks());
+#else
   mutex_enter(&trx_sys->mutex);
 
   ut_a(lock_validate_table_locks(&trx_sys->rw_trx_list));
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /* Iterate over all the record locks and validate the locks. We
   don't want to hog the lock_sys_t::mutex and the trx_sys_t::mutex.
@@ -5469,7 +5763,9 @@ static bool lock_validate() {
     }
   }
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   mutex_exit(&trx_sys->mutex);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   lock_mutex_exit();
 
   for (page_addr_set::const_iterator it = pages.begin(); it != pages.end();
@@ -5635,7 +5931,11 @@ static void lock_rec_convert_impl_to_expl_for_trx(
     trx_t *trx,               /*!< in/out: active transaction */
     ulint heap_no)            /*!< in: rec heap number to lock */
 {
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad(trx->is_referenced());
+#else
   ut_ad(trx_is_referenced(trx));
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   DEBUG_SYNC_C("before_lock_rec_convert_impl_to_expl_for_trx");
 
@@ -5688,11 +5988,80 @@ static void lock_rec_convert_impl_to_expl_for_trx(
   lock_mutex_exit();
   trx_mutex_exit(trx);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx->release_reference();
+#else
   trx_release_reference(trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   DEBUG_SYNC_C("after_lock_rec_convert_impl_to_expl_for_trx");
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** If a transaction has an implicit x-lock on a record, but no explicit x-lock
+set on the record, sets one for it.
+@param[in,out] caller_trx    current transaction
+@param[in]        block         buffer block of rec
+@param[in]        rec                 user record on page
+@param[in]        index                     index of record
+@param[in]        offsets                 rec_get_offsets(rec, index)
+@return whether caller_trx already holds an exclusive lock on rec */
+static bool lock_rec_convert_impl_to_expl(trx_t *caller_trx,
+                                          const buf_block_t *block,
+                                          const rec_t *rec, dict_index_t *index,
+                                          const ulint *offsets) {
+  trx_t *trx;
+
+  ut_ad(!lock_mutex_own());
+  ut_ad(page_rec_is_user_rec(rec));
+  ut_ad(rec_offs_validate(rec, index, offsets));
+  ut_ad(!page_rec_is_comp(rec) == !rec_offs_comp(offsets));
+
+  if (index->is_clustered()) {
+    trx_id_t trx_id;
+
+    trx_id = lock_clust_rec_some_has_impl(rec, index, offsets);
+
+   if (trx_id == 0) {
+      return false;
+    }
+    if (UNIV_UNLIKELY(trx_id == caller_trx->id)) {
+      return true;
+    }
+
+    trx = trx_sys->find(caller_trx, trx_id);
+  } else {
+    ut_ad(!dict_index_is_online_ddl(index));
+
+    trx = lock_sec_rec_some_has_impl(caller_trx, rec, index, offsets);
+    if (trx == caller_trx) {
+      trx->release_reference();
+      return true;
+    }
+
+    if (trx != nullptr) {
+      DEBUG_SYNC_C("lock_rec_convert_impl_to_expl_will_validate");
+      ut_d(lock_rec_other_trx_holds_expl(caller_trx, LOCK_S | LOCK_REC_NOT_GAP,
+                                         trx, rec, block));
+    }
+  }
+
+  if (trx != nullptr) {
+    ulint heap_no = page_rec_get_heap_no(rec);
+
+    ut_ad(trx->is_referenced());
+
+    /* If the transaction is still active and has no
+    explicit x-lock set on the record, set one for it.
+    trx cannot be committed until the ref count is zero. */
+
+    lock_rec_convert_impl_to_expl_for_trx(block, rec, index, offsets, trx,
+                                          heap_no);
+  }
+
+  return false;
+}
+#else
 /** If a transaction has an implicit x-lock on a record, but no explicit x-lock
 set on the record, sets one for it.
 @param[in]	block		buffer block of rec
@@ -5741,12 +6110,17 @@ static void lock_rec_convert_impl_to_expl(const buf_block_t *block,
                                           heap_no);
   }
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 void lock_rec_convert_active_impl_to_expl(const buf_block_t *block,
                                           const rec_t *rec, dict_index_t *index,
                                           const ulint *offsets, trx_t *trx,
                                           ulint heap_no) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx->reference();
+#else
   trx_reference(trx, true);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   lock_rec_convert_impl_to_expl_for_trx(block, rec, index, offsets, trx,
                                         heap_no);
 }
@@ -5786,7 +6160,15 @@ dberr_t lock_clust_rec_modify_check_and_lock(
   /* If a transaction has no explicit x-lock set on the record, set one
   for it */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (lock_rec_convert_impl_to_expl(thr_get_trx(thr), block, rec, index,
+                                    offsets)) {
+    /* We already hold an implicit exclusive lock. */
+    return DB_SUCCESS;
+  }
+#else
   lock_rec_convert_impl_to_expl(block, rec, index, offsets);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   lock_mutex_enter();
 
@@ -5909,11 +6291,23 @@ dberr_t lock_sec_rec_read_check_and_lock(
   if the max trx id for the page >= min trx id for the trx list or a
   database recovery is running. */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if ((page_get_max_trx_id(block->frame) >= trx_sys->get_min_trx_id() ||
+       recv_recovery_is_on()) &&
+      page_rec_is_supremum(rec) == 0u) {
+    if (lock_rec_convert_impl_to_expl(thr_get_trx(thr), block, rec, index,
+                                      offsets)) {
+      /* We already hold an implicit exclusive lock. */
+      return DB_SUCCESS;
+    }
+  }
+#else
   if ((page_get_max_trx_id(block->frame) >= trx_rw_min_trx_id() ||
        recv_recovery_is_on()) &&
       !page_rec_is_supremum(rec)) {
     lock_rec_convert_impl_to_expl(block, rec, index, offsets);
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   lock_mutex_enter();
 
@@ -5962,7 +6356,15 @@ dberr_t lock_clust_rec_read_check_and_lock(
   heap_no = page_rec_get_heap_no(rec);
 
   if (heap_no != PAGE_HEAP_NO_SUPREMUM) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+    if (lock_rec_convert_impl_to_expl(thr_get_trx(thr), block, rec, index,
+                                      offsets)) {
+      /* We already hold an implicit exclusive lock. */
+      return DB_SUCCESS;
+    }
+#else
     lock_rec_convert_impl_to_expl(block, rec, index, offsets);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   }
 
   DEBUG_SYNC_C("after_lock_clust_rec_read_check_and_lock_impl_to_expl");
@@ -6408,8 +6810,13 @@ void lock_trx_release_locks(trx_t *trx) /*!< in/out: transaction */
   check_trx_state(trx);
   ut_ad(trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY));
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (trx->is_referenced()) {
+    while (trx->is_referenced()) {
+#else
   if (trx_is_referenced(trx)) {
     while (trx_is_referenced(trx)) {
+#endif /* HAVE_LOCK_FREE_READVIEW */
       trx_mutex_exit(trx);
 
       DEBUG_SYNC_C("waiting_trx_is_not_referenced");
@@ -6422,7 +6829,11 @@ void lock_trx_release_locks(trx_t *trx) /*!< in/out: transaction */
     }
   }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad(!trx->is_referenced());
+#else
   ut_ad(!trx_is_referenced(trx));
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /* If the background thread trx_rollback_or_clean_recovered()
   is still active then there is a chance that the rollback
@@ -6496,6 +6907,61 @@ dberr_t lock_trx_handle_wait(trx_t *trx) /*!< in/out: trx lock state */
 }
 
 #ifdef UNIV_DEBUG
+#ifdef HAVE_LOCK_FREE_READVIEW
+struct lock_table_locks_lookup_arg {
+  const dict_table_t *table;
+  const lock_t *lock;
+};
+
+static bool lock_table_locks_lookup_callback(rw_trx_hash_element_t *element,
+                                             lock_table_locks_lookup_arg *arg) {
+  mutex_enter(&element->mutex);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    const lock_t *lock = nullptr;
+
+    check_trx_state(trx);
+
+    for (lock = UT_LIST_GET_FIRST(trx->lock.trx_locks); lock != nullptr;
+         lock = UT_LIST_GET_NEXT(trx_locks, lock)) {
+      ut_a(lock->trx == trx);
+
+      if (lock_get_type_low(lock) == LOCK_REC) {
+        ut_ad(!dict_index_is_online_ddl(lock->index) ||
+              lock->index->is_clustered());
+        if (lock->index->table == arg->table) {
+          arg->lock = lock;
+          break;
+        }
+      } else if (lock->tab_lock.table == arg->table) {  // NOLINT
+        arg->lock = lock;
+        break;
+      }
+    }
+  }
+  mutex_exit(&element->mutex);
+  return (arg->lock != nullptr);
+}
+
+/** Do an exhaustive check for any locks (table or rec) against the table.
+ @return lock if found */
+static const lock_t *lock_table_locks_lookup(
+    const dict_table_t *table) /*!< in: check if there are
+                               any locks held on records in
+                               this table or on the table
+                               itself */
+{
+  ut_a(table != nullptr);
+
+  lock_table_locks_lookup_arg arg = {table, nullptr};
+
+  trx_sys->rw_trx_hash.iterate(
+      reinterpret_cast<lf_hash_walk_func *>(lock_table_locks_lookup_callback),
+      &arg);
+
+  return (arg.lock);
+}
+#else
 /** Do an exhaustive check for any locks (table or rec) against the table.
  @return lock if found */
 static const lock_t *lock_table_locks_lookup(
@@ -6535,6 +7001,7 @@ static const lock_t *lock_table_locks_lookup(
 
   return (NULL);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #endif /* UNIV_DEBUG */
 
 /** Check if there are any locks (table or rec) against table.
@@ -6552,11 +7019,15 @@ bool lock_table_has_locks(
 
 #ifdef UNIV_DEBUG
   if (!has_locks) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+    ut_ad(lock_table_locks_lookup(table) == nullptr);
+#else
     mutex_enter(&trx_sys->mutex);
 
     ut_ad(!lock_table_locks_lookup(table, &trx_sys->rw_trx_list));
 
     mutex_exit(&trx_sys->mutex);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   }
 #endif /* UNIV_DEBUG */
 
diff --git a/storage/innobase/page/page0page.cc b/storage/innobase/page/page0page.cc
index 70521c3..e429953 100644
--- a/storage/innobase/page/page0page.cc
+++ b/storage/innobase/page/page0page.cc
@@ -2180,7 +2180,11 @@ ibool page_validate(
     trx_id_t max_trx_id = page_get_max_trx_id(page);
     /* This will be 0 during recv_apply_hashed_log_recs(TRUE),
     because the transaction system has not been initialized yet */
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_id_t sys_max_trx_id = trx_sys->get_max_trx_id();
+#else
     trx_id_t sys_max_trx_id = trx_sys_get_max_trx_id();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     if (max_trx_id == 0 ||
         (sys_max_trx_id != 0 && max_trx_id > sys_max_trx_id)) {
diff --git a/storage/innobase/read/read0read.cc b/storage/innobase/read/read0read.cc
index f5b6ff9..c543929 100644
--- a/storage/innobase/read/read0read.cc
+++ b/storage/innobase/read/read0read.cc
@@ -182,6 +182,7 @@ will mark their views as closed but not actually free their views.
 /** Minimum number of elements to reserve in ReadView::ids_t */
 static const ulint MIN_TRX_IDS = 32;
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 #ifdef UNIV_DEBUG
 /** Functor to validate the view list. */
 struct ViewCheck {
@@ -322,12 +323,24 @@ ReadView::ReadView()
   ut_d(m_view_low_limit_no = 0);
 }
 
+#else
+/**
+ReadView constructor */
+ReadView::ReadView()
+    : m_low_limit_id(),
+      m_up_limit_id(),
+      m_creator_trx_id(),
+      m_low_limit_no(),
+      m_state(READ_VIEW_STATE_CLOSED) {}
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
 /**
 ReadView destructor */
 ReadView::~ReadView() {
   // Do nothing
 }
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** Constructor
 @param size		Number of views to pre-allocate */
 MVCC::MVCC(ulint size) {
@@ -745,3 +758,122 @@ void MVCC::set_view_creator_trx_id(ReadView *view, trx_id_t id) {
 
   view->creator_trx_id(id);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
+#ifdef HAVE_LOCK_FREE_READVIEW
+void ReadView::copy(const ReadView &other) {
+  ut_ad(&other != this);
+  if (m_low_limit_no > other.m_low_limit_no) {
+    m_low_limit_no = other.m_low_limit_no;
+  }
+  if (m_low_limit_id > other.m_low_limit_id) {
+    m_low_limit_id = other.m_low_limit_id;
+  }
+
+  auto dst = m_ids.begin();
+  for (auto id : other.m_ids) {
+    if (id >= m_low_limit_id) {
+      break;
+    }
+
+  loop:
+    if (dst == m_ids.end()) {
+      m_ids.push_back(id);
+      dst = m_ids.end();
+      continue;
+    }
+    if (*dst < id) {
+      dst++;
+      goto loop;
+    } else if (*dst > id) {
+      dst = m_ids.insert(dst, id) + 1;
+    }
+  }
+  m_ids.erase(std::lower_bound(dst, m_ids.end(), m_low_limit_id), m_ids.end());
+
+  m_up_limit_id = m_ids.empty() ? m_low_limit_id : m_ids.front();
+  ut_ad(m_up_limit_id <= m_low_limit_id);
+}
+
+/** Creates a snapshot where exactly the transaction serialized before this
+point in time are seen in the view.
+
+@param[in,out] trx transaction */
+void ReadView::snapshot(trx_t *trx) {
+  trx_sys->snapshot_ids(trx, &m_ids, &m_low_limit_id, &m_low_limit_no);
+  std::sort(m_ids.begin(), m_ids.end());
+  m_up_limit_id = m_ids.empty() ? m_low_limit_id : m_ids.front();
+  ut_ad(m_up_limit_id <= m_low_limit_id);
+}
+
+/** Open a read view where exactly the transactions serialized before this point
+in time are seen in the view.
+
+View becomes visible to purge thread.
+
+@param[in,out] trx transaction*/
+void ReadView::open(trx_t *trx) {
+  ut_ad(this == &trx->read_view);
+  switch (m_state.load(std::memory_order_relaxed)) {
+    case READ_VIEW_STATE_OPEN:
+      ut_ad(!srv_read_only_mode);
+      return;
+    case READ_VIEW_STATE_CLOSED:
+      if (srv_read_only_mode) {
+        return;
+      }
+      /* Reuse closed view if there were no read-write transactions since (and
+      at) its creation time.
+
+      Original comment states: there is an inherent race between purge and this
+      thread.
+
+      To avoid this race we should've checked trx_sys->get_max_trx_id() and set
+      state to READ_VIEW_STATE_OPEN atomically under trx_sys->mutex protection.
+      But we'are cutting edges to achieve greate scalability.
+
+      There's at least two types of concurrent threads interested in this value:
+      purge coordinator thread (sees trx_sys_t::clone_oldest_view()) and InnoDB
+      monitor thread (see lock_trx_print_wait_and_mvcc_state()).
+
+      What bad things can happen because we allow this race?
+
+      Speculative execution may reorder state change before get_max_trx_id(). In
+      this case purge thread has short gap to clone outdated view. Which is
+      probably not that bad: it just won't be able to purge things that it was
+      actually allowed to purge for a short while.
+
+      This thread may as well get suspended after trx_sys->get_max_trx_id() and
+      before state is set to READ_VIEW_STATE_OPEN. New read-write transaction
+      may get started, committed and purged meanwhile. It is acceptable as well,
+      since this view doesn't see it. */
+      if (trx_is_autocommit_non_locking(trx) && m_ids.empty() &&
+          m_low_limit_id == trx_sys->get_max_trx_id()) {
+        goto reopen;
+      }
+
+      /* Can't reuse view, take new snapshot.
+
+      Alas this empty critical section is simplest way to make sure concurrent
+      purge thread completed snapshot copy. Of course purge thread may come
+      again and try to copy once again after we release this mutex, but in this
+      case it is guaranteed to see READ_VIEW_STATE_REGISTERED and thus it'll
+      skip this view.
+
+      This critical section can be replaced with new state, which purge thread
+      would set to inform us to wait until it completes snapshot. However it'd
+      complicate m_state even further. */
+      mutex_enter(&trx_sys->mutex);
+      mutex_exit(&trx_sys->mutex);
+      m_state.store(READ_VIEW_STATE_SNAPSHOT, std::memory_order_relaxed);
+      break;
+    default:
+      ut_ad(false);
+  }
+
+  snapshot(trx);
+reopen:
+  m_creator_trx_id = trx->id;
+  m_state.store(READ_VIEW_STATE_OPEN, std::memory_order_release);
+}
+#endif /* HAVE_LOCK_FREE_READVIEW */
diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc
index 20ac667..3a7c5c3 100644
--- a/storage/innobase/row/row0ins.cc
+++ b/storage/innobase/row/row0ins.cc
@@ -706,7 +706,9 @@ static void row_ins_foreign_trx_print(trx_t *trx) /*!< in: transaction */
   heap_size = mem_heap_get_size(trx->lock.lock_heap);
   lock_mutex_exit();
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   trx_sys_mutex_enter();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   mutex_enter(&dict_foreign_err_mutex);
   rewind(dict_foreign_err_file);
@@ -716,7 +718,9 @@ static void row_ins_foreign_trx_print(trx_t *trx) /*!< in: transaction */
   trx_print_low(dict_foreign_err_file, trx, 600, n_rec_locks, n_trx_locks,
                 heap_size);
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   trx_sys_mutex_exit();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   ut_ad(mutex_own(&dict_foreign_err_mutex));
 }
diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc
index a4394b5..06507cd 100644
--- a/storage/innobase/row/row0merge.cc
+++ b/storage/innobase/row/row0merge.cc
@@ -1915,6 +1915,24 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t
       ONLINE_INDEX_COMPLETE state between the time
       the DML thread has updated the clustered index
       but has not yet accessed secondary index. */
+#ifdef HAVE_LOCK_FREE_READVIEW
+      ut_ad(trx->read_view.is_open());
+
+      if (!trx->read_view.changes_visible(
+              row_get_rec_trx_id(rec, clust_index, offsets), old_table->name)) {
+        rec_t *old_vers;
+
+        row_vers_build_for_consistent_read(rec, &mtr, clust_index, &offsets,
+                                           &trx->read_view, &row_heap, row_heap,
+                                           &old_vers, nullptr, nullptr);
+
+        rec = old_vers;
+
+        if (rec == nullptr) {
+          continue;
+        }
+      }
+#else
       ut_ad(MVCC::is_view_active(trx->read_view));
 
       if (!trx->read_view->changes_visible(
@@ -1931,6 +1949,7 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t
           continue;
         }
       }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
       if (rec_get_deleted_flag(rec, dict_table_is_comp(old_table))) {
         /* This record was deleted in the latest
diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc
index 3188afb..a352401 100644
--- a/storage/innobase/row/row0mysql.cc
+++ b/storage/innobase/row/row0mysql.cc
@@ -4658,7 +4658,11 @@ dberr_t row_scan_index_for_mysql(row_prebuilt_t *prebuilt, dict_index_t *index,
       /* No INSERT INTO  ... SELECT  and non-locking selects only. */
       trx_start_if_not_started_xa(prebuilt->trx, false);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+      prebuilt->trx->read_view.open(prebuilt->trx);
+#else
       trx_assign_read_view(prebuilt->trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
       auto trx = prebuilt->trx;
 
diff --git a/storage/innobase/row/row0pread.cc b/storage/innobase/row/row0pread.cc
index 3105cad..3660db8 100644
--- a/storage/innobase/row/row0pread.cc
+++ b/storage/innobase/row/row0pread.cc
@@ -334,10 +334,17 @@ bool Parallel_reader::Scan_ctx::check_visibility(const rec_t *&rec,
                                                  mtr_t *mtr) {
   const auto table_name = m_config.m_index->table->name;
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(m_trx->read_view == nullptr || MVCC::is_view_active(m_trx->read_view));
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (m_trx->read_view.is_open()) {
+    ReadView* view = const_cast<ReadView*>(&m_trx->read_view);
+#else
   if (m_trx->read_view != nullptr) {
     auto view = m_trx->read_view;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     if (m_config.m_index->is_clustered()) {
       trx_id_t rec_trx_id;
diff --git a/storage/innobase/row/row0row.cc b/storage/innobase/row/row0row.cc
index edf01a4..3a7a4d6 100644
--- a/storage/innobase/row/row0row.cc
+++ b/storage/innobase/row/row0row.cc
@@ -376,6 +376,7 @@ static inline dtuple_t *row_build_low(ulint type, const dict_index_t *index,
   ut_ad(heap != NULL);
   ut_ad(index->is_clustered());
   ut_ad(!trx_sys_mutex_own());
+
   ut_ad(!col_map || col_table);
 
   if (!offsets) {
@@ -385,6 +386,11 @@ static inline dtuple_t *row_build_low(ulint type, const dict_index_t *index,
   }
 
 #if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_a(!rec_offs_any_null_extern(rec, offsets) ||
+       trx_sys->is_registered(current_trx(),
+                              row_get_rec_trx_id(rec, index, offsets)));
+#else
   /* Some blob refs can be NULL during crash recovery before
   trx_rollback_active() has completed execution, or when a concurrently
   executing insert or update has committed the B-tree mini-transaction
@@ -394,6 +400,7 @@ static inline dtuple_t *row_build_low(ulint type, const dict_index_t *index,
   insert or update statement.  */
   ut_a(!rec_offs_any_null_extern(rec, offsets) ||
        trx_rw_is_active(row_get_rec_trx_id(rec, index, offsets), NULL, false));
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
 
   if (type != ROW_COPY_POINTERS) {
diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc
index ede4d37..07f3e63 100644
--- a/storage/innobase/row/row0sel.cc
+++ b/storage/innobase/row/row0sel.cc
@@ -751,9 +751,15 @@ static void row_sel_build_committed_vers_for_mysql(
     prebuilt->old_vers_heap = mem_heap_create(rec_offs_size(*offsets));
   }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  row_vers_build_for_semi_consistent_read(
+      prebuilt->trx, rec, mtr, clust_index, offsets, offset_heap,
+      prebuilt->old_vers_heap, old_vers, vrow);
+#else
   row_vers_build_for_semi_consistent_read(rec, mtr, clust_index, offsets,
                                           offset_heap, prebuilt->old_vers_heap,
                                           old_vers, vrow);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 }
 
 /** Tests the conditions which determine when the index segment we are searching
@@ -2165,6 +2171,16 @@ que_thr_t *row_sel_step(que_thr_t *thr) /*!< in: query thread */
 
     if (node->consistent_read) {
       /* Assign a read view for the query */
+#ifdef HAVE_LOCK_FREE_READVIEW
+      trx_t *trx = thr_get_trx(thr);
+      trx->read_view.open(trx);
+
+      if (trx->read_view.is_open()) {
+        node->read_view = &trx->read_view;
+      } else {
+        node->read_view = nullptr;
+      }
+#else
       trx_assign_read_view(thr_get_trx(thr));
 
       if (thr_get_trx(thr)->read_view != NULL) {
@@ -2172,6 +2188,7 @@ que_thr_t *row_sel_step(que_thr_t *thr) /*!< in: query thread */
       } else {
         node->read_view = NULL;
       }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     } else {
       sym_node_t *table_node;
@@ -3346,9 +3363,15 @@ dberr_t Row_sel_get_clust_rec_for_mysql::operator()(
                                        trx_get_read_view(trx))) {
       if (clust_rec != cached_clust_rec) {
         /* The following call returns 'offsets' associated with 'old_vers' */
+#ifdef HAVE_LOCK_FREE_READVIEW
+        err = row_sel_build_prev_vers_for_mysql(
+            &trx->read_view, clust_index, prebuilt, clust_rec, offsets,
+            offset_heap, &old_vers, vrow, mtr, lob_undo);
+#else
         err = row_sel_build_prev_vers_for_mysql(
             trx->read_view, clust_index, prebuilt, clust_rec, offsets,
             offset_heap, &old_vers, vrow, mtr, lob_undo);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
         if (err != DB_SUCCESS) {
           goto err_exit;
@@ -4644,7 +4667,12 @@ dberr_t row_search_mvcc(byte *buf, page_cur_mode_t mode,
     if (trx->mysql_n_tables_locked == 0 && !prebuilt->ins_sel_stmt &&
         prebuilt->select_lock_type == LOCK_NONE &&
         trx->isolation_level > TRX_ISO_READ_UNCOMMITTED &&
-        MVCC::is_view_active(trx->read_view)) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+        trx->read_view.is_open())
+#else
+        MVCC::is_view_active(trx->read_view))
+#endif /* HAVE_LOCK_FREE_READVIEW */
+    {
       /* This is a SELECT query done as a consistent read,
       and the read view has already been allocated:
       let us try a search shortcut through the hash
@@ -4753,8 +4781,13 @@ dberr_t row_search_mvcc(byte *buf, page_cur_mode_t mode,
 
   ut_ad(!trx_is_started(trx) || trx->state == TRX_STATE_ACTIVE);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad(prebuilt->sql_stat_start || prebuilt->select_lock_type != LOCK_NONE ||
+        trx->read_view.is_open() || srv_read_only_mode);
+#else
   ut_ad(prebuilt->sql_stat_start || prebuilt->select_lock_type != LOCK_NONE ||
         MVCC::is_view_active(trx->read_view) || srv_read_only_mode);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   trx_start_if_not_started(trx, false);
 
@@ -4797,6 +4830,17 @@ dberr_t row_search_mvcc(byte *buf, page_cur_mode_t mode,
   if (!prebuilt->sql_stat_start) {
     /* No need to set an intention lock or assign a read view */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    if (!trx->read_view.is_open() && !srv_read_only_mode &&
+        prebuilt->select_lock_type == LOCK_NONE) {
+      ib::error(ER_IB_MSG_1031) << "MySQL is trying to perform a"
+                                   " consistent read but the read view is not"
+                                   " assigned!";
+      trx_print(stderr, trx, 600);
+      fputc('\n', stderr);
+      ut_error;
+    }
+#else
     if (!MVCC::is_view_active(trx->read_view) && !srv_read_only_mode &&
         prebuilt->select_lock_type == LOCK_NONE) {
       ib::error(ER_IB_MSG_1031) << "MySQL is trying to perform a"
@@ -4806,12 +4850,17 @@ dberr_t row_search_mvcc(byte *buf, page_cur_mode_t mode,
       fputc('\n', stderr);
       ut_error;
     }
+#endif /* HAVE_LOCK_FREE_READVIEW */
   } else if (prebuilt->select_lock_type == LOCK_NONE) {
     /* This is a consistent read */
     /* Assign a read view for the query */
 
     if (!srv_read_only_mode) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+      trx->read_view.open(trx);
+#else
       trx_assign_read_view(trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
     }
 
     prebuilt->sql_stat_start = FALSE;
@@ -5315,10 +5364,17 @@ rec_loop:
                                          trx_get_read_view(trx))) {
         rec_t *old_vers;
         /* The following call returns 'offsets' associated with 'old_vers' */
+#ifdef HAVE_LOCK_FREE_READVIEW
+        err = row_sel_build_prev_vers_for_mysql(
+            &trx->read_view, clust_index, prebuilt, rec, &offsets, &heap,
+            &old_vers, need_vrow ? &vrow : nullptr, &mtr,
+            prebuilt->get_lob_undo());
+#else
         err = row_sel_build_prev_vers_for_mysql(
             trx->read_view, clust_index, prebuilt, rec, &offsets, &heap,
             &old_vers, need_vrow ? &vrow : NULL, &mtr,
             prebuilt->get_lob_undo());
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
         if (err != DB_SUCCESS) {
           goto lock_wait_or_error;
@@ -5343,8 +5399,14 @@ rec_loop:
 
       ut_ad(!index->is_clustered());
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+      if (!srv_read_only_mode &&
+          !lock_sec_rec_cons_read_sees(rec, index, &trx->read_view))
+#else
       if (!srv_read_only_mode &&
-          !lock_sec_rec_cons_read_sees(rec, index, trx->read_view)) {
+          !lock_sec_rec_cons_read_sees(rec, index, trx->read_view))
+#endif /* HAVE_LOCK_FREE_READVIEW */
+        {
         /* We should look at the clustered index.
         However, as this is a non-locking read,
         we can skip the clustered index lookup if
diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc
index 5e950b2..ac7aa5f 100644
--- a/storage/innobase/row/row0upd.cc
+++ b/storage/innobase/row/row0upd.cc
@@ -3054,8 +3054,14 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t
     }
   }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad((row_get_rec_trx_id(rec, index, offsets) == trx->id) ||
+        lock_trx_has_rec_x_lock(thr, index->table, btr_pcur_get_block(pcur),
+                                page_rec_get_heap_no(rec)));
+#else
   ut_ad(lock_trx_has_rec_x_lock(thr, index->table, btr_pcur_get_block(pcur),
                                 page_rec_get_heap_no(rec)));
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /* NOTE: the following function calls will also commit mtr */
 
diff --git a/storage/innobase/row/row0vers.cc b/storage/innobase/row/row0vers.cc
index 42ab246..6a4b322 100644
--- a/storage/innobase/row/row0vers.cc
+++ b/storage/innobase/row/row0vers.cc
@@ -228,6 +228,9 @@ looking_for_match to the given sec_rec is found among versions created by trx_id
 or the one version before them
 */
 static bool row_vers_find_matching(
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_t *caller_trx, /*!< in/out: trx of current thread */
+#endif                 /* HAVE_LOCK_FREE_READVIEW */
     bool looking_for_match, const dict_index_t *const clust_index,
     const rec_t *const clust_rec, ulint *&clust_offsets,
     const dict_index_t *const sec_index, const rec_t *const sec_rec,
@@ -255,8 +258,13 @@ static bool row_vers_find_matching(
     /* The oldest visible clustered index version must not be
     delete-marked, because we never start a transaction by
     inserting a delete-marked record. */
+#ifdef HAVE_LOCK_FREE_READVIEW
+    ut_ad(prev_version != nullptr || rec_get_deleted_flag(version, comp) == 0 ||
+          !trx_sys->is_registered(caller_trx, trx_id));
+#else
     ut_ad(prev_version || !rec_get_deleted_flag(version, comp) ||
           !trx_rw_is_active(trx_id, NULL, false));
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     /* Free version and clust_offsets. */
     mem_heap_free(old_heap);
@@ -289,6 +297,9 @@ static bool row_vers_find_matching(
  trx_is_active() while holding lock_sys->mutex. */
 UNIV_INLINE
 trx_t *row_vers_impl_x_locked_low(
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_t *caller_trx,         /*!< in/out: trx of current thread */
+#endif                         /* HAVE_LOCK_FREE_READVIEW */
     const rec_t *const clust_rec,          /*!< in: clustered index record */
     const dict_index_t *const clust_index, /*!< in: the clustered index */
     const rec_t *const sec_rec,            /*!< in: secondary index record */
@@ -496,6 +507,26 @@ trx_t *row_vers_impl_x_locked_low(
   trx_id = row_get_rec_trx_id(clust_rec, clust_index, clust_offsets);
   corrupt = FALSE;
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_t *trx;
+
+  if (trx_id == caller_trx->id) {
+    trx = caller_trx;
+    trx->reference();
+  } else {
+    trx = trx_sys->find(caller_trx, trx_id);
+    if (trx == nullptr) {
+      /* The transaction that modified or inserted clust_rec is no
+      longer active, or it is corrupt: no implicit lock on rec */
+      if (corrupt != 0u) {
+        lock_report_trx_id_insanity(trx_id, clust_rec, clust_index,
+                                    clust_offsets, trx_sys->get_max_trx_id());
+      }
+      mem_heap_free(heap);
+      return 0;
+    }
+  }
+#else
   trx_t *trx = trx_rw_is_active(trx_id, &corrupt, true);
 
   if (trx == 0) {
@@ -508,6 +539,7 @@ trx_t *row_vers_impl_x_locked_low(
     mem_heap_free(heap);
     return 0;
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   comp = page_rec_is_comp(sec_rec);
   ut_ad(sec_index->table == clust_index->table);
@@ -516,10 +548,17 @@ trx_t *row_vers_impl_x_locked_low(
 
   bool looking_for_match = rec_get_deleted_flag(sec_rec, comp);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (!row_vers_find_matching(caller_trx, looking_for_match, clust_index,
+                              clust_rec, clust_offsets, sec_index, sec_rec,
+                              sec_offsets, comp, trx_id, mtr, heap)) {
+    trx->release_reference();
+#else
   if (!row_vers_find_matching(looking_for_match, clust_index, clust_rec,
                               clust_offsets, sec_index, sec_rec, sec_offsets,
                               comp, trx_id, mtr, heap)) {
     trx_release_reference(trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
     trx = 0;
   }
 
@@ -536,6 +575,9 @@ trx_t *row_vers_impl_x_locked_low(
  negatives. The caller must confirm all positive results by calling
  trx_is_active() while holding lock_sys->mutex. */
 trx_t *row_vers_impl_x_locked(
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_t *caller_trx,    /*!< in/out: trx of current thread */
+#endif                    /* HAVE_LOCK_FREE_READVIEW */
     const rec_t *rec,          /*!< in: record in a secondary index */
     const dict_index_t *index, /*!< in: the secondary index */
     const ulint *offsets)      /*!< in: rec_get_offsets(rec, index) */
@@ -575,10 +617,17 @@ trx_t *row_vers_impl_x_locked(
 
     trx = 0;
   } else {
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx = row_vers_impl_x_locked_low(caller_trx, clust_rec, clust_index, rec,
+                                     index, offsets, &mtr);
+
+    ut_ad(trx == nullptr || trx->is_referenced());
+#else
     trx = row_vers_impl_x_locked_low(clust_rec, clust_index, rec, index,
                                      offsets, &mtr);
 
     ut_ad(trx == 0 || trx_is_referenced(trx));
+#endif /* HAVE_LOCK_FREE_READVIEW */
   }
 
   mtr_commit(&mtr);
@@ -1357,6 +1406,9 @@ dberr_t row_vers_build_for_consistent_read(
 /** Constructs the last committed version of a clustered index record,
  which should be seen by a semi-consistent read. */
 void row_vers_build_for_semi_consistent_read(
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_t *caller_trx,        /*!< in/out: trx of current thread */
+#endif                        /* HAVE_LOCK_FREE_READVIEW */
     const rec_t *rec,         /*!< in: record in a clustered index; the
                               caller must have a latch on the page; this
                               latch locks the top of the stack of versions
@@ -1393,7 +1445,9 @@ void row_vers_build_for_semi_consistent_read(
   ut_ad(!vrow || !(*vrow));
 
   for (;;) {
+#ifndef HAVE_LOCK_FREE_READVIEW
     const trx_t *version_trx;
+#endif /* HAVE_LOCK_FREE_READVIEW */
     mem_heap_t *heap2;
     rec_t *prev_version;
     trx_id_t version_trx_id;
@@ -1403,6 +1457,9 @@ void row_vers_build_for_semi_consistent_read(
       rec_trx_id = version_trx_id;
     }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    if (!trx_sys->is_registered(caller_trx, version_trx_id))
+#else
     trx_sys_mutex_enter();
     version_trx = trx_get_rw_trx_by_id(version_trx_id);
     /* Because version_trx is a read-write transaction,
@@ -1415,7 +1472,9 @@ void row_vers_build_for_semi_consistent_read(
     }
     trx_sys_mutex_exit();
 
-    if (!version_trx) {
+    if (!version_trx)
+#endif /* HAVE_LOCK_FREE_READVIEW */
+    {
     committed_version_trx:
       /* We found a version that belongs to a
       committed transaction: return it. */
diff --git a/storage/innobase/srv/srv0srv.cc b/storage/innobase/srv/srv0srv.cc
index 9592955..3ee3a68 100644
--- a/storage/innobase/srv/srv0srv.cc
+++ b/storage/innobase/srv/srv0srv.cc
@@ -1421,9 +1421,14 @@ ibool srv_printf_innodb_monitor(
           ULINTPF " queries inside InnoDB, " ULINTPF " queries in queue\n",
           srv_conc_get_active_threads(), srv_conc_get_waiting_threads());
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  fprintf(file, ULINTPF " read views open inside InnoDB\n",
+          trx_sys->view_count());
+#else
   /* This is a dirty read, without holding trx_sys->mutex. */
   fprintf(file, ULINTPF " read views open inside InnoDB\n",
           trx_sys->mvcc->size());
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   n_reserved = fil_space_get_n_reserved_extents(0);
   if (n_reserved > 0) {
@@ -1636,6 +1641,10 @@ void srv_export_innodb_status(void) {
 
   export_vars.innodb_undo_tablespaces_implicit = FSP_IMPLICIT_UNDO_TABLESPACES;
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  export_vars.innodb_have_lock_free_readview = TRUE;
+#endif
+
   undo::spaces->s_lock();
 
   export_vars.innodb_undo_tablespaces_total = undo::spaces->size();
@@ -1660,15 +1669,23 @@ void srv_export_innodb_status(void) {
   transaction number. We are allowed to purge transactions with number
   below the low limit. */
   ReadView oldest_view;
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_sys->clone_oldest_view(&oldest_view);
+#else
   trx_sys->mvcc->clone_oldest_view(&oldest_view);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   trx_id_t low_limit_no = oldest_view.view_low_limit_no();
 
   rw_lock_s_unlock(&purge_sys->latch);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_id_t max_trx_no = trx_sys->rw_max_trx_no;
+#else
   mutex_enter(&trx_sys->mutex);
   /* Maximum transaction number added to history list for purge. */
   trx_id_t max_trx_no = trx_sys->rw_max_trx_no;
   mutex_exit(&trx_sys->mutex);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   if (done_trx_no == 0 || max_trx_no < done_trx_no) {
     export_vars.innodb_purge_trx_id_age = 0;
diff --git a/storage/innobase/srv/srv0start.cc b/storage/innobase/srv/srv0start.cc
index 9ed698d..c62f27f 100644
--- a/storage/innobase/srv/srv0start.cc
+++ b/storage/innobase/srv/srv0start.cc
@@ -1345,10 +1345,17 @@ dberr_t srv_undo_tablespaces_upgrade() {
   rsegs and undo tablespaces they are in from being deleted.
   These transactions must be either committed or rolled back by
   the mysql server.*/
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (trx_sys->found_prepared_trx()) {
+    ib::warn(ER_IB_MSG_1094);
+    return (DB_SUCCESS);
+  }
+#else
   if (trx_sys->n_prepared_trx > 0) {
     ib::warn(ER_IB_MSG_1094);
     return (DB_SUCCESS);
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   ib::info(ER_IB_MSG_1095, trx_sys_undo_spaces->size(),
            ulong{FSP_IMPLICIT_UNDO_TABLESPACES});
@@ -2880,9 +2887,15 @@ void srv_dict_recover_on_restart() {
   that the data dictionary tables will be free of any locks.
   The data dictionary latch should guarantee that there is at
   most one data dictionary transaction active at a time. */
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (srv_force_recovery < SRV_FORCE_NO_TRX_UNDO) {
+    trx_rollback_recovered(false);
+  }
+#else
   if (srv_force_recovery < SRV_FORCE_NO_TRX_UNDO && trx_sys_need_rollback()) {
     trx_rollback_or_clean_recovered(FALSE);
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /* Do after all DD transactions recovery, to get consistent metadata */
   apply_dynamic_metadata();
@@ -2963,6 +2976,16 @@ void srv_start_threads(bool bootstrap) {
     return;
   }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (!bootstrap && srv_force_recovery < SRV_FORCE_NO_TRX_UNDO) {
+    /* Rollback all recovered transactions that are
+    not in committed nor in XA PREPARE state. */
+    srv_threads.m_trx_recovery_rollback = os_thread_create(
+        trx_recovery_rollback_thread_key, trx_recovery_rollback_thread);
+
+    srv_threads.m_trx_recovery_rollback.start();
+  }
+#else
   if (!bootstrap && srv_force_recovery < SRV_FORCE_NO_TRX_UNDO &&
       trx_sys_need_rollback()) {
     /* Rollback all recovered transactions that are
@@ -2972,6 +2995,7 @@ void srv_start_threads(bool bootstrap) {
 
     srv_threads.m_trx_recovery_rollback.start();
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /* Create the master thread which does purge and other utility
   operations */
@@ -3096,7 +3120,11 @@ void srv_pre_dd_shutdown() {
     prepared transactions and we don't want to lose them. */
 
     for (uint32_t count = 0;; ++count) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+      const ulint total_trx = trx_sys->any_active_transactions();
+#else
       const ulint total_trx = trx_sys_any_active_transactions();
+#endif
 
       if (total_trx == 0) {
         break;
@@ -3187,7 +3215,11 @@ void srv_pre_dd_shutdown() {
 
   srv_is_being_shutdown = true;
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_a(trx_sys->any_active_transactions() == 0);
+#else
   ut_a(trx_sys_any_active_transactions() == 0);
+#endif
 
   DBUG_EXECUTE_IF("wait_for_threads_in_pre_dd_shutdown",
                   srv_shutdown_background_threads(););
@@ -3468,7 +3500,11 @@ void srv_shutdown() {
 
   ut_a(!srv_is_being_started);
   ut_a(srv_is_being_shutdown);
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_a(trx_sys->any_active_transactions() == 0);
+#else
   ut_a(trx_sys_any_active_transactions() == 0);
+#endif
 
   /* Ensure threads below have been stopped. */
   const auto threads_stopped_before_shutdown = {
diff --git a/storage/innobase/sync/sync0debug.cc b/storage/innobase/sync/sync0debug.cc
index 0267a9a..675d3ed 100644
--- a/storage/innobase/sync/sync0debug.cc
+++ b/storage/innobase/sync/sync0debug.cc
@@ -444,6 +444,9 @@ LatchDebug::LatchDebug() {
   LEVEL_MAP_INSERT(SYNC_REC_LOCK);
   LEVEL_MAP_INSERT(SYNC_THREADS);
   LEVEL_MAP_INSERT(SYNC_TRX);
+#ifdef HAVE_LOCK_FREE_READVIEW
+  LEVEL_MAP_INSERT(SYNC_RW_TRX_HASH_ELEMENT);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   LEVEL_MAP_INSERT(SYNC_TRX_SYS);
   LEVEL_MAP_INSERT(SYNC_LOCK_SYS);
   LEVEL_MAP_INSERT(SYNC_LOCK_WAIT_SYS);
@@ -481,6 +484,9 @@ LatchDebug::LatchDebug() {
   LEVEL_MAP_INSERT(SYNC_TABLE);
   LEVEL_MAP_INSERT(SYNC_STATS_AUTO_RECALC);
   LEVEL_MAP_INSERT(SYNC_DICT_AUTOINC_MUTEX);
+#ifdef HAVE_SCALABLE_LOCK_MGR
+  LEVEL_MAP_INSERT(SYNC_DICT_TABLE_LOCKS_MUTEX);
+#endif /* HAVE_SCALABLE_LOCK_MGR */
   LEVEL_MAP_INSERT(SYNC_DICT);
   LEVEL_MAP_INSERT(SYNC_PARSER);
   LEVEL_MAP_INSERT(SYNC_FTS_CACHE);
@@ -705,6 +711,9 @@ Latches *LatchDebug::check_order(const latch_t *latch,
     case SYNC_THREADS:
     case SYNC_LOCK_SYS:
     case SYNC_LOCK_WAIT_SYS:
+#ifdef HAVE_LOCK_FREE_READVIEW
+    case SYNC_RW_TRX_HASH_ELEMENT:
+#endif /* HAVE_LOCK_FREE_READVIEW */
     case SYNC_TRX_SYS:
     case SYNC_IBUF_BITMAP_MUTEX:
     case SYNC_TEMP_SPACE_RSEG:
@@ -1490,6 +1499,11 @@ static void sync_latch_meta_init() UNIV_NOTHROW {
 
   LATCH_ADD_MUTEX(TEST_MUTEX, SYNC_NO_ORDER_CHECK, PFS_NOT_INSTRUMENTED);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  LATCH_ADD_MUTEX(RW_TRX_HASH_ELEMENT, SYNC_RW_TRX_HASH_ELEMENT,
+                  rw_trx_hash_element_mutex_key);
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
   latch_id_t id = LATCH_ID_NONE;
 
   /* The array should be ordered on latch ID.We need to
diff --git a/storage/innobase/sync/sync0sync.cc b/storage/innobase/sync/sync0sync.cc
index 4330303..86bd999 100644
--- a/storage/innobase/sync/sync0sync.cc
+++ b/storage/innobase/sync/sync0sync.cc
@@ -125,6 +125,9 @@ mysql_pfs_key_t trx_pool_manager_mutex_key;
 mysql_pfs_key_t temp_pool_manager_mutex_key;
 mysql_pfs_key_t lock_mutex_key;
 mysql_pfs_key_t lock_wait_mutex_key;
+#ifdef HAVE_LOCK_FREE_READVIEW
+mysql_pfs_key_t rw_trx_hash_element_mutex_key;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 mysql_pfs_key_t trx_sys_mutex_key;
 mysql_pfs_key_t srv_sys_mutex_key;
 mysql_pfs_key_t srv_threads_mutex_key;
diff --git a/storage/innobase/trx/trx0i_s.cc b/storage/innobase/trx/trx0i_s.cc
index e0dbe8e..c17fb67 100644
--- a/storage/innobase/trx/trx0i_s.cc
+++ b/storage/innobase/trx/trx0i_s.cc
@@ -860,6 +860,29 @@ static void trx_i_s_cache_clear(
 
 /** Fetches the data needed to fill the 3 INFORMATION SCHEMA tables into the
  table cache buffer. Cache must be locked for write. */
+#ifdef HAVE_LOCK_FREE_READVIEW
+static void fetch_data_into_cache_low(
+    trx_i_s_cache_t *cache, /*!< in/out: cache */
+    const trx_t *trx)       /*!< in/out: transaction */
+{
+  i_s_locks_row_t *requested_lock_row;
+
+  assert_trx_nonlocking_or_in_list(trx);
+  if (add_trx_relevant_locks_to_cache(cache, trx, &requested_lock_row) != 0u) {
+    auto trx_row = static_cast<i_s_trx_row_t *>(
+        table_cache_create_empty_row(&cache->innodb_trx, cache));
+    if (trx_row != nullptr) {
+      if (fill_trx_row(trx_row, trx, requested_lock_row, cache) == TRUE) {
+        return;
+      }
+      --cache->innodb_trx.rows_used;
+    }
+  }
+
+  /* memory could not be allocated */
+  cache->is_truncated = TRUE;
+}
+#else
 static void fetch_data_into_cache_low(
     trx_i_s_cache_t *cache,  /*!< in/out: cache */
     bool read_write,         /*!< in: only read-write
@@ -923,22 +946,39 @@ static void fetch_data_into_cache_low(
     trx_mutex_exit(trx);
   }
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Fetches the data needed to fill the 3 INFORMATION SCHEMA tables into the
  table cache buffer. Cache must be locked for write. */
 static void fetch_data_into_cache(trx_i_s_cache_t *cache) /*!< in/out: cache */
 {
   ut_ad(lock_mutex_own());
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(trx_sys_mutex_own());
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   trx_i_s_cache_clear(cache);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  mutex_enter(&trx_sys->mutex);
+  for (const trx_t *trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx != nullptr;
+       trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+    if (trx_is_started(trx) && trx != purge_sys->query->trx) {
+      fetch_data_into_cache_low(cache, trx);
+      if (cache->is_truncated == TRUE) {
+        break;
+      }
+    }
+  }
+  mutex_exit(&trx_sys->mutex);
+#else
   /* Capture the state of the read-write transactions. This includes
   internal transactions too. They are not on mysql_trx_list */
   fetch_data_into_cache_low(cache, true, &trx_sys->rw_trx_list);
 
   /* Capture the state of the read-only active transactions */
   fetch_data_into_cache_low(cache, false, &trx_sys->mysql_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   cache->is_truncated = FALSE;
 }
@@ -957,11 +997,15 @@ int trx_i_s_possibly_fetch_data_into_cache(
 
   lock_mutex_enter();
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   trx_sys_mutex_enter();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   fetch_data_into_cache(cache);
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   trx_sys_mutex_exit();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   lock_mutex_exit();
 
diff --git a/storage/innobase/trx/trx0purge.cc b/storage/innobase/trx/trx0purge.cc
index f6d2193..583ccb0 100644
--- a/storage/innobase/trx/trx0purge.cc
+++ b/storage/innobase/trx/trx0purge.cc
@@ -240,7 +240,11 @@ void trx_purge_sys_create(ulint n_purge_threads, purge_pq_t *purge_queue) {
 
   new (&purge_sys->view) ReadView();
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_sys->clone_oldest_view();
+#else
   trx_sys->mvcc->clone_oldest_view(&purge_sys->view);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   purge_sys->view_active = true;
 
@@ -348,7 +352,11 @@ void trx_purge_add_update_undo_to_history(
                  undo_header + TRX_UNDO_HISTORY_NODE, mtr);
 
   if (update_rseg_history_len) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_sys->rseg_history_len.fetch_add(n_added_logs);
+#else
     os_atomic_increment_ulint(&trx_sys->rseg_history_len, n_added_logs);
+#endif /* HAVE_LOCK_FREE_READVIEW */
     srv_wake_purge_thread_if_not_active();
   }
 
@@ -384,7 +392,11 @@ static void trx_purge_remove_log_hdr(trx_rsegf_t *rseg_hdr,
   flst_remove(rseg_hdr + TRX_RSEG_HISTORY, log_hdr + TRX_UNDO_HISTORY_NODE,
               mtr);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_sys->rseg_history_len.fetch_sub(1);
+#else
   os_atomic_decrement_ulint(&trx_sys->rseg_history_len, 1);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 }
 
 /** Frees a rollback segment which is in the history list.
@@ -1655,7 +1667,9 @@ static void trx_purge_rseg_get_next_history_log(
     mtr_commit(&mtr);
 
 #ifdef UNIV_DEBUG
+#ifndef HAVE_LOCK_FREE_READVIEW
     trx_sys_mutex_enter();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     /* Add debug code to track history list corruption reported
     on the MySQL mailing list on Nov 9, 2004. The fut0lst.cc
@@ -1680,7 +1694,9 @@ static void trx_purge_rseg_get_next_history_log(
                                   " threads to expedite purging of undo logs.";
     }
 
+#ifndef HAVE_LOCK_FREE_READVIEW
     trx_sys_mutex_exit();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #endif
     return;
   }
@@ -2214,7 +2230,11 @@ ulint trx_purge(ulint n_purge_threads, /*!< in: number of purge tasks
 
   purge_sys->view_active = false;
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_sys->clone_oldest_view();
+#else
   trx_sys->mvcc->clone_oldest_view(&purge_sys->view);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   purge_sys->view_active = true;
 
diff --git a/storage/innobase/trx/trx0roll.cc b/storage/innobase/trx/trx0roll.cc
index 861d3f3..fe805b2 100644
--- a/storage/innobase/trx/trx0roll.cc
+++ b/storage/innobase/trx/trx0roll.cc
@@ -186,11 +186,19 @@ static dberr_t trx_rollback_low(trx_t *trx) {
     case TRX_STATE_FORCED_ROLLBACK:
     case TRX_STATE_NOT_STARTED:
       trx->will_lock = 0;
+#ifdef HAVE_LOCK_FREE_READVIEW
+      ut_ad(trx->mysql_thd != nullptr);
+#else
       ut_ad(trx->in_mysql_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
       return (DB_SUCCESS);
 
     case TRX_STATE_ACTIVE:
+#ifdef HAVE_LOCK_FREE_READVIEW
+      ut_ad(trx->mysql_thd != nullptr);
+#else
       ut_ad(trx->in_mysql_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
       assert_trx_nonlocking_or_in_list(trx);
       /* Check an validate that undo is available for GTID. */
       trx_undo_gtid_add_update_undo(trx, false, true);
@@ -285,7 +293,11 @@ dberr_t trx_rollback_last_sql_stat_for_mysql(
   here, because the statement rollback should be invoked for a
   running active MySQL transaction that is associated with the
   current thread. */
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad(trx->mysql_thd != nullptr);
+#else
   ut_ad(trx->in_mysql_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   switch (trx->state) {
     case TRX_STATE_FORCED_ROLLBACK:
@@ -390,7 +402,11 @@ executed after the savepoint */
   dberr_t err;
 
   ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad(trx->mysql_thd != nullptr);
+#else
   ut_ad(trx->in_mysql_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /* Free all savepoints strictly later than savep. */
 
@@ -437,7 +453,11 @@ dberr_t trx_rollback_to_savepoint_for_mysql(
   here, because the savepoint rollback should be invoked for a
   running active MySQL transaction that is associated with the
   current thread. */
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad(trx->mysql_thd != nullptr);
+#else
   ut_ad(trx->in_mysql_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   savep = trx_savepoint_find(trx, savepoint_name);
 
@@ -524,7 +544,11 @@ dberr_t trx_release_savepoint_for_mysql(
   trx_named_savept_t *savep;
 
   ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad(trx->mysql_thd != nullptr);
+#else
   ut_ad(trx->in_mysql_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   savep = trx_savepoint_find(trx, savepoint_name);
 
@@ -581,7 +605,9 @@ static void trx_rollback_active(trx_t *trx) /*!< in/out: transaction */
 
   ut_a(thr == que_fork_start_command(fork));
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   trx_sys_mutex_enter();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   trx_roll_crash_recv_trx = trx;
 
@@ -591,7 +617,9 @@ static void trx_rollback_active(trx_t *trx) /*!< in/out: transaction */
 
   rows_to_undo = trx_roll_max_undo_no;
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   trx_sys_mutex_exit();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   if (rows_to_undo > 1000000000) {
     rows_to_undo = rows_to_undo / 1000000;
@@ -623,6 +651,71 @@ static void trx_rollback_active(trx_t *trx) /*!< in/out: transaction */
   trx_roll_crash_recv_trx = NULL;
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+static bool trx_rollback_recovered_callback(rw_trx_hash_element_t *element,
+                                            std::vector<trx_t *> *trx_list) {
+  mutex_enter(&element->mutex);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    mutex_enter(&trx->mutex);
+    if (trx->is_recovered && trx_state_eq(trx, TRX_STATE_ACTIVE)) {
+      ut_ad(trx != trx_dummy_sess->trx);
+      trx_list->push_back(trx);
+    }
+    mutex_exit(&trx->mutex);
+  }
+  mutex_exit(&element->mutex);
+  return false;
+}
+
+/** Rollback any incomplete transactions which were encountered in crash
+ recovery.
+
+ If the transaction already was committed, then we cleanup a possible insert
+ undo log. If the transaction was not yet committed, then we roll it back.
+
+ Note: For XA recovered transactions, we rely on MySQL to do rollback. They
+ will be in TRX_STATE_PREPARED state. If the server is shutdown and they are
+ still lingering in trx_sys_t::trx_list then the shutdown will hang.
+
+ @param [in] all true=roll back all recovered active transactions;
+                 fasle=roll back any incomplete dictionary transaction */
+void trx_rollback_recovered(bool all) {
+  std::vector<trx_t *> trx_list;
+
+  ut_a(srv_force_recovery < SRV_FORCE_NO_TRX_UNDO);
+
+  /* Collect list of recovered ACTIVE transaction ids first. Once collected, no
+  other thread is allowed to modify or remove these transactions from
+  rw_trx_hash.*/
+  trx_sys->rw_trx_hash.iterate_no_dups(
+      reinterpret_cast<lf_hash_walk_func *>(trx_rollback_recovered_callback),
+      &trx_list);
+
+  while (!trx_list.empty()) {
+    trx_t *trx = trx_list.back();
+    trx_list.pop_back();
+
+#ifdef UNIV_DEBUG
+    ut_ad(trx != nullptr);
+    trx_mutex_enter(trx);
+    ut_ad(trx->is_recovered && trx_state_eq(trx, TRX_STATE_ACTIVE));
+    trx_mutex_exit(trx);
+#endif /* UNIV_DEBUG */
+
+    if (all || trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) {
+      trx_rollback_active(trx);
+      if (trx->error_state != DB_SUCCESS) {
+        trx->error_state = DB_SUCCESS;
+        trx_sys->deregister_rw(trx);
+        trx_free_at_shutdown(trx);
+      } else {
+        trx_free(trx);
+      }
+    }
+  }
+}
+#else
 /** Rollback or clean up any resurrected incomplete transactions. It assumes
  that the caller holds the trx_sys_t::mutex and it will release the
  lock if it does a clean up or rollback.
@@ -730,6 +823,7 @@ void trx_rollback_or_clean_recovered(
                                 " completed";
   }
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Rollback or clean up any incomplete transactions which were
 encountered in crash recovery.  If the transaction already was
@@ -746,7 +840,17 @@ void trx_recovery_rollback_thread() {
 
   ut_ad(!srv_read_only_mode);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (trx_sys->rw_trx_hash.size() > 0) {
+    ib::info(ER_IB_MSG_1189) << "Starting in background the rollback"
+                                " of uncommitted transactions";
+    trx_rollback_recovered(true);
+    ib::info(ER_IB_MSG_1190) << "Rollback of non-prepared transactions"
+                                " completed";
+  }
+#else
   trx_rollback_or_clean_recovered(TRUE);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   destroy_thd(thd);
 }
diff --git a/storage/innobase/trx/trx0sys.cc b/storage/innobase/trx/trx0sys.cc
index f806dbd..8c5584f 100644
--- a/storage/innobase/trx/trx0sys.cc
+++ b/storage/innobase/trx/trx0sys.cc
@@ -49,6 +49,10 @@ this program; if not, write to the Free Software Foundation, Inc.,
 #include "srv0srv.h"
 #include "srv0start.h"
 #include "trx0purge.h"
+#ifdef HAVE_LOCK_FREE_READVIEW
+#include "trx0roll.h"
+#include "clone0clone.h"
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #include "trx0rseg.h"
 #include "trx0trx.h"
 #include "trx0undo.h"
@@ -67,7 +71,12 @@ void ReadView::check_trx_id_sanity(trx_id_t id, const table_name_t &name) {
     return;
   }
 
-  if (id >= trx_sys->max_trx_id) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+  if (id >= trx_sys->get_max_trx_id())
+#else
+  if (id >= trx_sys->max_trx_id)
+#endif /* HAVE_LOCK_FREE_READVIEW */
+  {
     ib::warn(ER_IB_MSG_1196)
         << "A transaction id"
         << " in a record of table " << name << " is newer than the"
@@ -99,15 +108,22 @@ void trx_sys_flush_max_trx_id(void) {
   mtr_t mtr;
   trx_sysf_t *sys_header;
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(trx_sys_mutex_own());
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   if (!srv_read_only_mode) {
     mtr_start(&mtr);
 
     sys_header = trx_sysf_get(&mtr);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    mlog_write_ull(sys_header + TRX_SYS_TRX_ID_STORE, trx_sys->get_max_trx_id(),
+                   &mtr);
+#else
     mlog_write_ull(sys_header + TRX_SYS_TRX_ID_STORE, trx_sys->max_trx_id,
                    &mtr);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     mtr_commit(&mtr);
   }
@@ -124,6 +140,42 @@ void trx_sys_persist_gtid_num(trx_id_t gtid_trx_no) {
   mtr.commit();
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+trx_id_t trx_sys_oldest_trx_no() {
+  /* Get the oldest transaction from serialisation list. */
+  trx_id_t max_trx_id = 0;
+  for (auto trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx != NULL;
+    trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+      if (trx->id == 0 || trx->state != TRX_STATE_ACTIVE) {
+          continue;
+      }
+      if (trx->no != TRX_ID_MAX && trx->no > max_trx_id) {
+          max_trx_id = trx->no;
+      }
+  }
+  return max_trx_id > 0 ? max_trx_id : trx_sys->get_max_trx_id();
+}
+
+void trx_sys_get_binlog_prepared(std::vector<trx_id_t> &trx_ids) {
+  mutex_enter(&trx_sys->mutex);
+  /* Exit fast if no prepared transaction. */
+  if (!trx_sys->found_prepared_trx()) {
+    mutex_exit(&trx_sys->mutex);
+    return;
+  }
+  /* Check and find binary log prepared transaction. */
+  for (auto trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx != NULL;
+       trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+    if (trx->state != TRX_STATE_ACTIVE && trx->state != TRX_STATE_PREPARED) {
+        continue;
+    }
+    if (trx_state_eq(trx, TRX_STATE_PREPARED) && trx_is_mysql_xa(trx)) {
+      trx_ids.push_back(trx->id);
+    }
+  }
+  mutex_exit(&trx_sys->mutex);
+}
+#else
 trx_id_t trx_sys_oldest_trx_no() {
   ut_ad(trx_sys_mutex_own());
   /* Get the oldest transaction from serialisation list. */
@@ -151,6 +203,7 @@ void trx_sys_get_binlog_prepared(std::vector<trx_id_t> &trx_ids) {
   }
   trx_sys_mutex_exit();
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Read binary log positions from buffer passed.
 @param[in]	binlog_buf	binary log buffer from trx sys page
@@ -440,19 +493,36 @@ purge_pq_t *trx_sys_init_at_db_start(void) {
 
   sys_header = trx_sysf_get(&mtr);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_id_t max_trx_id =
+        2 * TRX_SYS_TRX_ID_WRITE_MARGIN +
+        ut_uint64_align_up(
+            mach_read_from_8(sys_header + TRX_SYS_TRX_ID_STORE),  // NOLINT
+            TRX_SYS_TRX_ID_WRITE_MARGIN);
+    trx_sys->init_max_trx_id(max_trx_id);
+#else
   trx_sys->max_trx_id =
       2 * TRX_SYS_TRX_ID_WRITE_MARGIN +
       ut_uint64_align_up(mach_read_from_8(sys_header + TRX_SYS_TRX_ID_STORE),
                          TRX_SYS_TRX_ID_WRITE_MARGIN);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   mtr.commit();
 
 #ifdef UNIV_DEBUG
+#ifdef HAVE_LOCK_FREE_READVIEW
+  /* max_trx_id is the next transaction ID to assign. Initialize maximum
+  transaction number to one less if all transactions are already purged. */
+  if (trx_sys->rw_max_trx_no == 0) {
+    trx_sys->rw_max_trx_no = trx_sys->get_max_trx_id() - 1;
+  }
+#else
   /* max_trx_id is the next transaction ID to assign. Initialize maximum
   transaction number to one less if all transactions are already purged. */
   if (trx_sys->rw_max_trx_no == 0) {
     trx_sys->rw_max_trx_no = trx_sys->max_trx_id - 1;
   }
+#endif
 #endif /* UNIV_DEBUG */
 
   trx_dummy_sess = sess_open();
@@ -463,6 +533,44 @@ purge_pq_t *trx_sys_init_at_db_start(void) {
   the debug code (assertions). We are still running in single threaded
   bootstrap mode. */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  mutex_enter(&trx_sys->mutex);
+
+  if (UT_LIST_GET_LEN(trx_sys->trx_list) > 0) {
+    const trx_t *trx;
+
+    for (trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx != nullptr;
+         trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+      if (trx->state != TRX_STATE_ACTIVE && trx->state != TRX_STATE_PREPARED) {
+        /* trx_dummy_sess is a transaction in TRX_STATE_NOT_STARTED state. */
+        continue;
+      }
+      ut_ad(trx->is_recovered);
+      ut_ad(trx->rsegs.m_redo.rseg->trx_ref_count > 0);
+      ut_ad(trx_sys->find(nullptr, trx->id, false) != nullptr);
+
+      if (trx_state_eq(trx, TRX_STATE_ACTIVE)) {
+        rows_to_undo += trx->undo_no;
+      }
+    }
+
+    if (rows_to_undo > 1000000000) {
+      unit = "M";
+      rows_to_undo = rows_to_undo / 1000000;
+    }
+
+    ib::info(ER_IB_MSG_1198)
+        << UT_LIST_GET_LEN(trx_sys->trx_list)
+        << " transaction(s) which must be rolled back or"
+           " cleaned up in total "
+        << rows_to_undo << unit << " row operations to undo";
+
+    ib::info(ER_IB_MSG_1199)
+        << "Trx id counter is " << trx_sys->get_max_trx_id();
+  }
+
+  mutex_exit(&trx_sys->mutex);
+#else
   trx_sys_mutex_enter();
 
   if (UT_LIST_GET_LEN(trx_sys->rw_trx_list) > 0) {
@@ -495,10 +603,334 @@ purge_pq_t *trx_sys_init_at_db_start(void) {
   trx_sys->found_prepared_trx = trx_sys->n_prepared_trx > 0;
 
   trx_sys_mutex_exit();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   return (purge_queue);
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** Constructor callback for lock-free allocator.
+
+Object is just allocated and is not yet accessible via rw_trx_hash by
+concurrent threads. Object can be reused multiple times before it is freed.
+Every time object is being reused initialize() callback is called. */
+void rw_trx_hash_t::rw_trx_hash_constructor(uchar *arg) {
+  new (arg + LF_HASH_OVERHEAD) rw_trx_hash_element_t();
+}
+
+/** Destructor callback for lock-free allocator.
+
+Object is about to be freed and is not accessible via rw_trx_hash by
+concurrent threads. */
+void rw_trx_hash_t::rw_trx_hash_destructor(uchar *arg) {
+  reinterpret_cast<rw_trx_hash_element_t *>(arg + LF_HASH_OVERHEAD)
+      ->~rw_trx_hash_element_t();
+}
+
+/** Destructor callback for lock-free allocator.
+
+This destructor is used at shutdown. It frees remaining transaction objects.
+
+XA PREPARED transactions may remain if they haven't been committed or rolled
+back. ACTIVE transactions may remain if startup was interrupted or server is
+running in read-only mode or for certain srv_force_recovery levels. */
+void rw_trx_hash_t::rw_trx_hash_shutdown_destructor(uchar *arg) {
+  auto element =
+      reinterpret_cast<rw_trx_hash_element_t *>(arg + LF_HASH_OVERHEAD);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    ut_ad(trx_state_eq(trx, TRX_STATE_PREPARED) ||
+          (trx_state_eq(trx, TRX_STATE_ACTIVE) &&
+           (srv_is_being_started || srv_read_only_mode ||
+            (srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO))));
+    trx_free_at_shutdown(trx);
+  }
+  element->~rw_trx_hash_element_t();
+}
+
+/** Initializer callback for lock-free hash.
+
+Object is not yet accessible via rw_trx_hash by concurrent threads, but is
+about to become such. Object id can be changed only by this callback and
+remains the same until all pins to this object are released.
+
+Object trx can be changed to 0 by erase() under object mutex protection,
+which indicates it is about to be removed from lock-free hash and become not
+accessible by concurrent threads. */
+void rw_trx_hash_t::rw_trx_hash_initialize(rw_trx_hash_element_t *element,
+                                           trx_t *trx) {
+  ut_ad(element->trx == nullptr);
+  element->trx = trx;
+  element->id = trx->id;
+  element->no = TRX_ID_MAX;
+  trx->rw_trx_hash_element = element;
+}
+
+/** Gets LF_HASH pins.
+
+Pins are used to protect object from being destroyed or reused. They are
+normally stored in trx object for quick access. If caller doesn't have trx
+available, we try to get it using current_trx(). If caller doesn't have trx at
+all, temporary pins are allocated. */
+LF_PINS *rw_trx_hash_t::get_pins(trx_t *trx) {
+  if (trx->rw_trx_hash_pins == nullptr) {
+    trx->rw_trx_hash_pins = lf_hash_get_pins(&hash);
+    ut_a(trx->rw_trx_hash_pins != nullptr);
+  }
+
+  return trx->rw_trx_hash_pins;
+}
+
+bool rw_trx_hash_t::eliminate_duplicates(rw_trx_hash_element_t *element,
+                                         eliminate_duplicates_arg *arg) {
+  for (auto id : arg->ids) {
+    if (id == element->id) {
+      return false;
+    }
+  }
+
+  arg->ids.push_back(element->id);
+  return arg->action(element, arg->argument);
+}
+
+#ifdef UNIV_DEBUG
+void rw_trx_hash_t::validate_element(trx_t *trx) {
+  ut_ad(!trx->read_only || trx->rsegs.m_redo.rseg == nullptr);
+  ut_ad(!trx_is_autocommit_non_locking(trx));
+  mutex_enter(&trx->mutex);
+  ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE) ||
+        trx_state_eq(trx, TRX_STATE_PREPARED));
+  mutex_exit(&trx->mutex);
+}
+
+bool rw_trx_hash_t::debug_iterator(rw_trx_hash_element_t *element,
+                                   debug_iterator_arg *arg) {
+  mutex_enter(&element->mutex);
+  if (element->trx != nullptr) {
+    validate_element(element->trx);
+  }
+  mutex_exit(&element->mutex);
+  return arg->action(element, arg->argument);
+}
+#endif /* UNIV_DEBUG */
+
+void rw_trx_hash_t::init() {
+  lf_hash_init(&hash, sizeof(rw_trx_hash_element_t), LF_HASH_UNIQUE, 0,
+               sizeof(trx_id_t), nullptr, &my_charset_bin);
+  hash.alloc.constructor = rw_trx_hash_constructor;
+  hash.alloc.destructor = rw_trx_hash_destructor;
+  hash.initialize =
+      reinterpret_cast<lf_hash_init_func *>(rw_trx_hash_initialize);
+}
+
+void rw_trx_hash_t::destroy() {
+  hash.alloc.destructor = rw_trx_hash_shutdown_destructor;
+  lf_hash_destroy(&hash);
+}
+
+/** Releases LF_HASH pins.
+
+Must be called by thread that owns trx_t object when the later is being
+"detached" from thread (e.g. released to the pool by trx_free()). Can be
+called earlier if thread is expected not to use rw_trx_hash.
+
+Since pins are not allowed to be transferred to another thread,
+initialisation thread calls this for recovered transactions. */
+void rw_trx_hash_t::put_pins(trx_t *trx) {
+  if (trx->rw_trx_hash_pins != nullptr) {
+    lf_hash_put_pins(trx->rw_trx_hash_pins);
+    trx->rw_trx_hash_pins = nullptr;
+  }
+}
+
+/** Finds trx object in lock-free hash with given id.
+
+Only ACTIVE or PREPARED trx objects may participate in hash. Nevertheless the
+transaction may get committed before this method returns.
+
+With do_ref_count == false the caller may dereference returned trx pointer
+only if lock_sys.mutex was acquired before calling find().
+
+With do_ref_count == true caller dereferemce trx even if it is not holding
+lock_sys.mutex. Caller is responsible for calling trx->release_reference()
+when it is done playing with trx.
+
+Ideally this method should get caller rw_trx_hash_pins along with trx object
+as a parameter, similar to insert() and erase(). However most callers lose trx
+early in their call chains and it is not that easy to pass them through.
+
+So we take more expensive approach: get trx through current_thd()->ha_data.
+Some threads don't have trx attached to THD, and at least server
+initialisation thread, fts_optimize_thread, srv_master_thread,
+dict_stats_thread, srv_monitor_thread, btr_defragment_thread don't even have
+THD at all. For such cases we allocate pins only for duration of search and
+free them immediately.
+
+This has negative performance impact and should be fixed eventually (by
+passing caller_trx as a parameter). Still stream of DML is more or less Ok.
+
+@return pointer to trx or nullptr if not found */
+trx_t *rw_trx_hash_t::find(trx_t *caller_trx, trx_id_t trx_id,
+                           bool do_ref_count) {
+  /* In Taurus, purge will reset DB_TRX_ID to 0 when the histroy is lost.
+  Read/Write transactions will always have a nonzero trx_t::id; there the
+  value 0 is reserved for transactions that did not write or lock anything
+  yet.
+
+  The caller should already have handled trx_id==0 specially. */
+  ut_ad(trx_id > 0);
+  if (caller_trx != nullptr && caller_trx->id == trx_id) {
+    if (do_ref_count) {
+      caller_trx->reference();
+    }
+    return caller_trx;
+  }
+
+  trx_t *trx = nullptr;
+  LF_PINS *pins =
+      (caller_trx != nullptr) ? get_pins(caller_trx) : lf_hash_get_pins(&hash);
+  ut_a(pins != nullptr);
+
+  rw_trx_hash_element_t *element = reinterpret_cast<rw_trx_hash_element_t *>(
+      lf_hash_search(&hash, pins, reinterpret_cast<const void *>(&trx_id),
+                     sizeof(trx_id_t)));
+  if (element != nullptr) {
+    mutex_enter(&element->mutex);
+    lf_hash_search_unpin(pins);
+    trx = element->trx;
+    if (trx == nullptr) {
+    } else if (UNIV_UNLIKELY(trx_id != trx->id)) {
+      trx = nullptr;
+    } else {
+      if (do_ref_count) {
+        trx->reference();
+      }
+      ut_d(validate_element(trx));
+    }
+    mutex_exit(&element->mutex);
+  } else {
+    lf_hash_search_unpin(pins);
+  }
+  if (caller_trx == nullptr) {
+    lf_hash_put_pins(pins);
+  }
+  return trx;
+}
+
+/** Inserts trx to lock-free hash.
+
+Object becomes accessible via rw_trx_hash. */
+void rw_trx_hash_t::insert(trx_t *trx) {
+  ut_d(validate_element(trx));
+  int res = lf_hash_insert(&hash, get_pins(trx), static_cast<void *>(trx));
+  ut_a(res == 0);
+}
+
+/** Removes trx from lock-free hash.
+
+Object becomes not accessible via rw_trx_hash. But it still can be pinned by
+concurrent find(), which is supposed to release it immediately after it sees
+object trx is nullptr. */
+void rw_trx_hash_t::erase(trx_t *trx) {
+  ut_d(validate_element(trx));
+  mutex_enter(&trx->rw_trx_hash_element->mutex);
+  trx->rw_trx_hash_element->trx = nullptr;
+  mutex_exit(&trx->rw_trx_hash_element->mutex);
+  int res =
+      lf_hash_delete(&hash, get_pins(trx), static_cast<const void *>(&trx->id),
+                     sizeof(trx_id_t));
+  ut_a(res == 0);
+}
+
+/** Returns the number of elements in the hash.
+
+The number is exact only if hash is protected against concurrent modifications
+(e.g., single threaded startup or hash is protected by some mutex). Otherwise
+the number maybe used as a hint only, because it may change even before this
+method returns. */
+uint32_t rw_trx_hash_t::size() {
+  return static_cast<uint32_t>(hash.count.load(std::memory_order_relaxed));
+}
+
+/** Iterates the hash.
+
+@param caller_trx used to get/set pins
+@param action     called for every element in hash
+@param argument   opque argument passed to action
+
+May return the same element multiple times if hash is under contention. If
+caller doesn't like to see the same transaction multiple times, it has to call
+iterate_no_dups() instead.
+
+May return element with committed transaction. If caller doesn't like to see
+committed transactions, it has to skip those under element mutex:
+
+  mutex_enter(&element->mutex);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    // trx is protected against commit in this branch
+  }
+  mutex_exit(&element->mutex);
+
+May miss concurrently inserted transactions.
+
+@return 0 if iteration completed successfuly, or 1 if iteration was
+interrupted (action returned true) */
+int rw_trx_hash_t::iterate(trx_t *caller_trx, const lf_hash_walk_func *action,
+                           const void *argument) {
+  LF_PINS *pins =
+      (caller_trx != nullptr) ? get_pins(caller_trx) : lf_hash_get_pins(&hash);
+  ut_a(pins != nullptr);
+#ifdef UNIV_DEBUG
+  debug_iterator_arg debug_arg = {action, const_cast<void *>(argument)};
+  action = reinterpret_cast<lf_hash_walk_func *>(debug_iterator);
+  argument = &debug_arg;
+#endif /* UNIV_DEBUG */
+  int res = lf_hash_iterate(&hash, pins, action, argument);
+  if (caller_trx == nullptr) {
+    lf_hash_put_pins(pins);
+  }
+  return res;
+}
+
+int rw_trx_hash_t::iterate(const lf_hash_walk_func *action, const void *argument) {
+  return iterate(current_trx(), action, argument);
+}
+
+/** Iterates the hash and eliminates duplicate elements.
+
+@sa iterate() */
+int rw_trx_hash_t::iterate_no_dups(trx_t *caller_trx, lf_hash_walk_func *action,
+                                   void *argument) {
+  eliminate_duplicates_arg arg(size() + 32, action, argument);
+  return iterate(caller_trx,
+                 reinterpret_cast<lf_hash_walk_func *>(eliminate_duplicates),
+                 &arg);
+}
+
+int rw_trx_hash_t::iterate_no_dups(lf_hash_walk_func *action, void *argument) {
+  return iterate_no_dups(current_trx(), action, argument);
+}
+
+/** Creates the trx_sys instance and initializes purge_queue and mutex. */
+void trx_sys_create() {
+  ut_ad(trx_sys == nullptr);
+
+  trx_sys = static_cast<trx_sys_t *>(ut_zalloc_nokey(sizeof(*trx_sys)));
+
+  mutex_create(LATCH_ID_TRX_SYS, &trx_sys->mutex);
+
+  UT_LIST_INIT(trx_sys->trx_list, &trx_t::trx_list);
+
+  new (&trx_sys->rsegs) Rsegs();
+  trx_sys->rsegs.set_empty();
+
+  new (&trx_sys->tmp_rsegs) Rsegs();
+  trx_sys->tmp_rsegs.set_empty();
+
+  trx_sys->rw_trx_hash.init();
+}
+#else
 /** Creates the trx_sys instance and initializes purge_queue and mutex. */
 void trx_sys_create(void) {
   ut_ad(trx_sys == NULL);
@@ -528,6 +960,7 @@ void trx_sys_create(void) {
   new (&trx_sys->tmp_rsegs) Rsegs();
   trx_sys->tmp_rsegs.set_empty();
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Creates and initializes the transaction system at the database creation. */
 void trx_sys_create_sys_pages(void) {
@@ -540,6 +973,151 @@ void trx_sys_create_sys_pages(void) {
   mtr_commit(&mtr);
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** @return total number of active (non-prepared) transactions. */
+ulint trx_sys_t::any_active_transactions() {
+  ulint total_trx = 0;
+
+  mutex_enter(&mutex);
+  for (trx_t *trx = UT_LIST_GET_FIRST(trx_list); trx != nullptr;
+       trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+    if (trx->state == TRX_STATE_COMMITTED_IN_MEMORY ||
+        (trx->state == TRX_STATE_ACTIVE && trx->id > 0)) {
+      total_trx++;
+    }
+
+    if (srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO) {
+      if (trx_state_eq(trx, TRX_STATE_ACTIVE) && trx->is_recovered) {
+        total_trx--;
+      }
+    }
+  }
+  mutex_exit(&mutex);
+  return total_trx;
+}
+
+/** Clones the oldest view and stores it in view.
+
+No need to call ReadView::close(). The caller owns the view that is passed in.
+This function is called by purge thread to determine whether it should purge the
+delete marked record or not. */
+void trx_sys_t::clone_oldest_view(ReadView *view) {
+  if (view == NULL) {
+      purge_sys->view.snapshot(nullptr);
+  }
+  mutex_enter(&mutex);
+  /* Find oldest view. */
+  for (const trx_t *trx = UT_LIST_GET_FIRST(trx_list); trx != nullptr;
+       trx = UT_LIST_GET_NEXT(trx_list, trx)) {
+    int32_t state;
+    while ((state = trx->read_view.get_state()) == READ_VIEW_STATE_SNAPSHOT) {
+      ut_delay(1);
+    }
+
+    if (state == READ_VIEW_STATE_OPEN) {
+      if (view == NULL) {
+          purge_sys->view.copy(trx->read_view);
+      } else {
+          view->copy(trx->read_view);
+      }
+    }
+  }
+  mutex_exit(&mutex);
+  /* Update view to block purging transaction till GTID is persisted. */
+  auto &gtid_persistor = clone_sys->get_gtid_persistor();
+  auto gtid_oldest_trxno = gtid_persistor.get_oldest_trx_no();
+  purge_sys->view.reduce_low_limit(gtid_oldest_trxno);
+}
+
+struct trx_sys_found_prepared_trx_callback_arg {
+  uint count;
+};
+
+static bool trx_sys_found_prepared_trx_callback(
+    rw_trx_hash_element_t *element,
+    trx_sys_found_prepared_trx_callback_arg *arg) {
+  mutex_enter(&element->mutex);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    if (trx_state_eq(trx, TRX_STATE_PREPARED)) {
+      arg->count++;
+    }
+  }
+  mutex_exit(&element->mutex);
+  return false;
+}
+
+/** @return true if found prepared transaction(s). */
+bool trx_sys_t::found_prepared_trx() {
+  trx_sys_found_prepared_trx_callback_arg arg = {0};
+
+  trx_sys->rw_trx_hash.iterate(reinterpret_cast<lf_hash_walk_func *>(
+                                   trx_sys_found_prepared_trx_callback),
+                               &arg);
+
+  return (arg.count > 0);
+}
+
+trx_id_t trx_sys_t::get_new_trx_id_no_refresh() {
+  /* TODO wcy: why is this function called when doing undo? */
+
+  /* VERY important: after the database is started, max_trx_id value is
+  divisible by TRX_SYS_TRX_ID_WRITE_MARGIN, and the following if
+  will evaluate to TRUE when this function is first time called,
+  and the value for trx id will be written to disk-based header!
+  Thus trx id values will not overlap when the database is
+  repeatedly started! */
+
+  if (get_max_trx_id() % TRX_SYS_TRX_ID_WRITE_MARGIN == 0) {
+    trx_sys_flush_max_trx_id();
+  }
+
+  return max_trx_id.fetch_add(1, std::memory_order_relaxed);
+}
+
+/*********************************************************************
+Shutdown/Close the transaction system. */
+void trx_sys_close() {
+  ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS);
+
+  if (trx_sys == nullptr) {
+    return;
+  }
+
+  ulint size = trx_sys->view_count();
+
+  if (size > 0) {
+    ib::error(ER_IB_MSG_1201) << "All read views were not closed before"
+                                 " shutdown: "
+                              << size << " read views open";
+  }
+
+  if (trx_dummy_sess != nullptr) {
+    sess_close(trx_dummy_sess);
+    trx_dummy_sess = nullptr;
+  }
+
+  trx_purge_sys_close();
+
+  /* Free the double write data structures. */
+  buf_dblwr_free();
+
+  trx_sys->rw_trx_hash.destroy();
+
+  /* There can't be any active transactions. */
+  trx_sys->rsegs.~Rsegs();
+
+  trx_sys->tmp_rsegs.~Rsegs();
+
+  ut_a(UT_LIST_GET_LEN(trx_sys->trx_list) == 0);
+
+  /* We used placement new to create this mutex. Call the destructor. */
+  mutex_free(&trx_sys->mutex);
+
+  ut_free(trx_sys);
+  trx_sys = nullptr;
+}
+#else
 /*********************************************************************
 Shutdown/Close the transaction system. */
 void trx_sys_close(void) {
@@ -690,6 +1268,7 @@ bool trx_sys_validate_trx_list() {
   return (true);
 }
 #endif /* UNIV_DEBUG */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 #endif /* !UNIV_HOTBACKUP */
 
 /** A list of undo tablespace IDs found in the TRX_SYS page. These are the
diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc
index 4af1f5d..b2ce5af 100644
--- a/storage/innobase/trx/trx0trx.cc
+++ b/storage/innobase/trx/trx0trx.cc
@@ -110,7 +110,11 @@ committed.
                             list of things we need to clean up during
                             trx_erase_lists.
 */
+#ifdef HAVE_LOCK_FREE_READVIEW
+static void trx_release_impl_and_expl_locks(trx_t *trx, bool atShutdown = false);
+#else
 static void trx_release_impl_and_expl_locks(trx_t *trx, bool serialized);
+#endif
 
 /** Set flush observer for the transaction
 @param[in,out]	trx		transaction struct
@@ -205,7 +209,11 @@ static void trx_init(trx_t *trx) {
 
   trx->last_sql_stat_start.least_undo_no = 0;
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  ut_ad(!trx->read_view.is_open());
+#else
   ut_ad(!MVCC::is_view_active(trx->read_view));
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   trx->lock.rec_cached = 0;
 
@@ -252,6 +260,13 @@ struct TrxFactory {
 
     new (&trx->lock.table_locks) lock_pool_t();
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    new (&trx->read_view) ReadView();
+
+    trx->rw_trx_hash_element = nullptr;
+    trx->rw_trx_hash_pins = nullptr;
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
     trx_init(trx);
 
     trx->state = TRX_STATE_NOT_STARTED;
@@ -279,8 +294,10 @@ struct TrxFactory {
   @param trx the transaction for which to release resources */
   static void destroy(trx_t *trx) {
     ut_a(trx->magic_n == TRX_MAGIC_N);
+#ifndef HAVE_LOCK_FREE_READVIEW
     ut_ad(!trx->in_rw_trx_list);
     ut_ad(!trx->in_mysql_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     ut_a(trx->lock.wait_lock == NULL);
     ut_a(trx->lock.wait_thr == NULL);
@@ -305,7 +322,12 @@ struct TrxFactory {
 
     trx->mod_tables.~trx_mod_tables_t();
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+      ut_ad(!trx->read_view.is_open());
+    trx->read_view.~ReadView();
+#else
     ut_ad(trx->read_view == NULL);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     if (!trx->lock.rec_pool.empty()) {
       /* See lock_trx_alloc_locks() why we only free
@@ -345,8 +367,10 @@ struct TrxFactory {
 
     ut_ad(trx->mysql_thd == 0);
 
+#ifndef HAVE_LOCK_FREE_READVIEW
     ut_ad(!trx->in_rw_trx_list);
     ut_ad(!trx->in_mysql_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     ut_a(trx->lock.wait_thr == NULL);
     ut_a(trx->lock.wait_lock == NULL);
@@ -448,6 +472,11 @@ static trx_t *trx_create_low() {
   /* We just got trx from pool, it should be non locking */
   ut_ad(trx->will_lock == 0);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    ut_ad(trx->state == TRX_STATE_NOT_STARTED);
+  ut_ad(trx->rw_trx_hash_pins == nullptr);
+#endif /* HAVE_LOCK_FREE_READVIEW */
+
   trx->persists_gtid = false;
 
   trx->api_trx = false;
@@ -475,12 +504,77 @@ static trx_t *trx_create_low() {
   trx_free(). */
   ut_a(trx->mod_tables.size() == 0);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_sys->register_trx(trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   return (trx);
 }
 
 /**
 Release a trx_t instance back to the pool.
 @param trx the instance to release. */
+#ifdef HAVE_LOCK_FREE_READVIEW
+void trx_free(trx_t *&trx) {
+  ut_ad(!trx->declared_to_be_inside_innodb);
+  ut_ad(trx->n_mysql_tables_in_use == 0);
+  ut_ad(trx->mysql_n_tables_locked == 0);
+  ut_ad(!trx->internal);
+
+  if (trx->declared_to_be_inside_innodb) {
+    ib::error(ER_IB_MSG_1270)
+        << "Freeing a trx (" << trx << ", " << trx_get_id_for_print(trx)
+        << ") which is declared"
+           " to be processing inside InnoDB";
+
+    trx_print(stderr, trx, 600);
+    putc('\n', stderr);
+
+    /* This is an error but not a fatal error. We must keep the coutners like
+    srv_conc.n_active accurate. */
+    srv_conc_force_exit_innodb(trx);
+  }
+
+  if (trx->n_mysql_tables_in_use > 0 || trx->mysql_n_tables_locked > 0) {
+    ib::error(ER_IB_MSG_1270)
+        << "MySQL is freeing a thd though"
+           " trx->n_mysql_tables_in_use is "
+        << trx->n_mysql_tables_in_use << " and trx->mysql_n_tables_locked is "
+        << trx->mysql_n_tables_locked << ".";
+
+    trx_print(stderr, trx, 600);
+    ut_print_buf(stderr, trx, sizeof(trx_t));
+    putc('\n', stderr);
+  }
+
+  trx->dict_operation = TRX_DICT_OP_NONE;
+  assert_trx_is_inactive(trx);
+
+  trx_sys->deregister_trx(trx);
+  assert_trx_is_free(trx);
+
+  trx_sys->rw_trx_hash.put_pins(trx);
+  trx->mysql_thd = nullptr;
+
+  // FIXME: We need to avoid this heap free/alloc for each commit.
+  if (trx->lock.autoinc_locks != nullptr) {
+    ut_ad(ib_vector_is_empty(trx->lock.autoinc_locks));
+    /* We allocated a dedicated heap for the vector. */
+    ib_vector_free(trx->lock.autoinc_locks);
+    trx->lock.autoinc_locks = nullptr;
+  }
+
+  trx->mod_tables.clear();
+
+  ut_ad(trx->is_dd_trx == false);
+
+  /* trx locking state should have been reset before returning trx
+  to pool */
+  ut_ad(trx->will_lock == 0);
+
+  trx_pools->mem_free(trx);
+  trx = nullptr;
+}
+#else
 static void trx_free(trx_t *&trx) {
   assert_trx_is_free(trx);
 
@@ -507,6 +601,7 @@ static void trx_free(trx_t *&trx) {
 
   trx = NULL;
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Creates a transaction object for background operations by the master thread.
  @return own: transaction object */
@@ -527,12 +622,14 @@ trx_t *trx_allocate_for_mysql(void) {
 
   trx = trx_allocate_for_background();
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   trx_sys_mutex_enter();
 
   ut_d(trx->in_mysql_trx_list = TRUE);
   UT_LIST_ADD_FIRST(trx_sys->mysql_trx_list, trx);
 
   trx_sys_mutex_exit();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   return (trx);
 }
@@ -570,6 +667,7 @@ static void trx_validate_state_before_free(trx_t *trx) {
   assert_trx_is_inactive(trx);
 }
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** Free and initialize a transaction object instantiated during recovery.
 @param[in,out]	trx	transaction object to free and initialize */
 void trx_free_resurrected(trx_t *trx) {
@@ -579,6 +677,7 @@ void trx_free_resurrected(trx_t *trx) {
 
   trx_free(trx);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Free a transaction that was allocated by background or user threads.
 @param[in,out]	trx	transaction object to free */
@@ -588,6 +687,32 @@ void trx_free_for_background(trx_t *trx) {
   trx_free(trx);
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** At shutdown, frees a transaction object. */
+void trx_free_at_shutdown(trx_t *trx) {
+  ut_ad(trx->is_recovered);
+  ut_a(trx_state_eq(trx, TRX_STATE_PREPARED) ||
+       trx_state_eq(trx, TRX_STATE_ACTIVE));
+  ut_a(trx->magic_n == TRX_MAGIC_N);
+
+  trx_release_impl_and_expl_locks(trx, true);
+  trx_undo_free_at_shutdown(trx);
+
+  ut_a(!trx->read_only);
+
+  DBUG_LOG("trx", "Free prepared: " << trx);
+  trx->state = TRX_STATE_NOT_STARTED;
+
+  /* Undo trx_resurrect_table_locks(). */
+  lock_trx_lock_list_init(&trx->lock.trx_locks);
+
+  /* Note: This vector is not guaranteed to be empty because the transaction
+  was never committed and therefore lock_trx_release() was not called. */
+  trx->lock.table_locks.clear();
+
+  trx_free(trx);
+}
+#else
 /** At shutdown, frees a transaction object that is in the PREPARED state. */
 void trx_free_prepared(trx_t *trx) /*!< in, own: trx object */
 {
@@ -609,6 +734,7 @@ void trx_free_prepared(trx_t *trx) /*!< in, own: trx object */
 
   trx_free(trx);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Disconnect a transaction from MySQL and optionally mark it as if
 it's been recovered. For the marking the transaction must be in prepared state.
@@ -619,6 +745,18 @@ finally freed.
 @param[in]	prepared	boolean value to specify whether trx is
                                 for recovery or not. */
 inline void trx_disconnect_from_mysql(trx_t *trx, bool prepared) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx->read_view.close();
+
+  if (prepared) {
+    ut_ad(trx_state_eq(trx, TRX_STATE_PREPARED));
+
+    trx->is_recovered = true;
+    trx->mysql_thd = nullptr;
+    /* todo/fixme: suggest to do it at innodb prepare */
+    trx->will_lock = 0;
+  }
+#else
   trx_sys_mutex_enter();
 
   ut_ad(trx->in_mysql_trx_list);
@@ -642,6 +780,7 @@ inline void trx_disconnect_from_mysql(trx_t *trx, bool prepared) {
   }
 
   trx_sys_mutex_exit();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 }
 
 /** Disconnect a transaction from MySQL.
@@ -793,7 +932,9 @@ static trx_t *trx_resurrect_insert(
 
       if (srv_force_recovery == 0) {
         trx->state = TRX_STATE_PREPARED;
+#ifndef HAVE_LOCK_FREE_READVIEW
         ++trx_sys->n_prepared_trx;
+#endif /* HAVE_LOCK_FREE_READVIEW */
       } else {
         ib::info(ER_IB_MSG_1205) << "Since innodb_force_recovery"
                                     " > 0, we will force a rollback.";
@@ -857,7 +998,9 @@ static void trx_resurrect_update_in_prepared_state(
     ut_ad(trx->state != TRX_STATE_FORCED_ROLLBACK);
 
     if (trx_state_eq(trx, TRX_STATE_NOT_STARTED)) {
+#ifndef HAVE_LOCK_FREE_READVIEW
       ++trx_sys->n_prepared_trx;
+#endif /* HAVE_LOCK_FREE_READVIEW */
     } else {
       ut_ad(trx_state_eq(trx, TRX_STATE_PREPARED));
     }
@@ -950,7 +1093,17 @@ static void trx_resurrect(trx_rseg_t *rseg) {
        undo = UT_LIST_GET_NEXT(undo_list, undo)) {
     trx = trx_resurrect_insert(undo, rseg);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+      trx_sys->rw_trx_hash.insert(trx);
+    trx_sys->rw_trx_hash.put_pins(trx);
+#ifdef UNIV_DEBUG
+    if (trx->id > trx_sys->rw_max_trx_no) {
+      trx_sys->rw_max_trx_no = trx->id;
+    }
+#endif /* UNIV_DEBUG */
+#else
     trx_sys_rw_trx_add(trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     trx_resurrect_table_ids(trx, &trx->rsegs.m_redo, undo);
   }
@@ -958,6 +1111,30 @@ static void trx_resurrect(trx_rseg_t *rseg) {
   /* Ressurrect transactions that were doing updates. */
   for (undo = UT_LIST_GET_FIRST(rseg->update_undo_list); undo != NULL;
        undo = UT_LIST_GET_NEXT(undo_list, undo)) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+      /* Check the trx_sys->rw_trx_hash first. */
+    trx_t *trx = trx_sys->find(nullptr, undo->trx_id, false);
+
+    if (trx == nullptr) {
+      trx = trx_allocate_for_background();
+
+      ut_d(trx->start_file = __FILE__);
+      ut_d(trx->start_line = __LINE__);
+
+      trx_resurrect_update(trx, undo, rseg);
+
+      trx_sys->rw_trx_hash.insert(trx);
+      trx_sys->rw_trx_hash.put_pins(trx);
+    } else {
+      trx_resurrect_update(trx, undo, rseg);
+    }
+
+#ifdef UNIV_DEBUG
+    if (trx->id > trx_sys->rw_max_trx_no) {
+      trx_sys->rw_max_trx_no = trx->id;
+    }
+#endif /* UNIV_DEBUG */
+#else
     /* Check the trx_sys->rw_trx_set first. */
     trx_sys_mutex_enter();
 
@@ -975,6 +1152,7 @@ static void trx_resurrect(trx_rseg_t *rseg) {
     trx_resurrect_update(trx, undo, rseg);
 
     trx_sys_rw_trx_add(trx);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     trx_resurrect_table_ids(trx, &trx->rsegs.m_redo, undo);
   }
@@ -1006,6 +1184,7 @@ void trx_lists_init_at_db_start(void) {
   }
   undo::spaces->s_unlock();
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   TrxIdSet::iterator end = trx_sys->rw_trx_set.end();
 
   for (TrxIdSet::iterator it = trx_sys->rw_trx_set.begin(); it != end; ++it) {
@@ -1018,6 +1197,7 @@ void trx_lists_init_at_db_start(void) {
 
     UT_LIST_ADD_FIRST(trx_sys->rw_trx_list, it->m_trx);
   }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 }
 
 /** Get next redo rollback segment in round-robin fashion.
@@ -1185,6 +1365,9 @@ void trx_assign_rseg_temp(trx_t *trx) {
       srv_read_only_mode ? nullptr : get_next_temp_rseg();
 
   if (trx->id == 0) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_sys->register_rw(trx);
+#else
     mutex_enter(&trx_sys->mutex);
 
     trx->id = trx_sys_get_new_trx_id();
@@ -1194,6 +1377,7 @@ void trx_assign_rseg_temp(trx_t *trx) {
     trx_sys->rw_trx_set.insert(TrxTrack(trx->id, trx));
 
     mutex_exit(&trx_sys->mutex);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   }
 }
 
@@ -1256,6 +1440,16 @@ static void trx_start_low(
   ut_a(ib_vector_is_empty(trx->lock.autoinc_locks));
   ut_a(trx->lock.table_locks.empty());
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    /* No other thread can access this trx object through rw_trx_hash, thus we
+  don't need trx_sys.mutex protection for that purposes. Still this trx can be
+  found through trx_sys->trx_list, which means state change must be protected by
+  e.g. trx->mutex.
+
+  For now we update it without mutex protection, because original code did it in
+  this way. It has to be reviewed and fixed properly. */
+  trx->state = TRX_STATE_ACTIVE;
+#else
   /* If this transaction came from trx_allocate_for_mysql(),
   trx->in_mysql_trx_list would hold. In that case, the trx->state
   change must be protected by the trx_sys->mutex, so that
@@ -1267,6 +1461,7 @@ static void trx_start_low(
   e.g., the transaction state can be set earlier but we are forced to
   set it under the protection of the trx_sys_t::mutex because some
   trx list assertions are triggered unnecessarily. */
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /* By default all transactions are in the read-only list unless they
   are non-locking auto-commit read only transactions or background
@@ -1281,6 +1476,17 @@ static void trx_start_low(
     /* Temporary rseg is assigned only if the transaction
     updates a temporary table */
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+      ut_ad(trx->rsegs.m_redo.rseg != nullptr || srv_read_only_mode ||
+          srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
+
+    trx_sys->register_rw(trx);
+#ifdef UNIV_DEBUG
+    if (trx->id > trx_sys->rw_max_trx_no) {
+      trx_sys->rw_max_trx_no = trx->id;
+    }
+#endif /* UNIV_DEBUG */
+#else
     trx_sys_mutex_enter();
 
     trx->id = trx_sys_get_new_trx_id();
@@ -1301,6 +1507,7 @@ static void trx_start_low(
     ut_ad(trx_sys_validate_trx_list());
 
     trx_sys_mutex_exit();
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   } else {
     trx->id = 0;
@@ -1311,6 +1518,10 @@ static void trx_start_low(
       to write to the temporary table. */
 
       if (read_write) {
+#ifdef HAVE_LOCK_FREE_READVIEW
+          ut_ad(!srv_read_only_mode);
+        trx_sys->register_rw(trx);
+#else
         trx_sys_mutex_enter();
 
         ut_ad(!srv_read_only_mode);
@@ -1322,13 +1533,18 @@ static void trx_start_low(
         trx_sys->rw_trx_set.insert(TrxTrack(trx->id, trx));
 
         trx_sys_mutex_exit();
+#endif /* HAVE_LOCK_FREE_READVIEW */
       }
 
+#ifndef HAVE_LOCK_FREE_READVIEW
       trx->state = TRX_STATE_ACTIVE;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     } else {
       ut_ad(!read_write);
+#ifndef HAVE_LOCK_FREE_READVIEW
       trx->state = TRX_STATE_ACTIVE;
+#endif /* HAVE_LOCK_FREE_READVIEW */
     }
   }
 
@@ -1346,6 +1562,194 @@ static void trx_start_low(
   MONITOR_INC(MONITOR_TRX_ACTIVE);
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** Set the transaction serialisation number.
+ @return true if the transaction number was added to the serialisation_list. */
+static void trx_serialise(
+    trx_t *trx,                         /*!< in/out: transaction */
+    mtr_t *mtr,                         /*!< in/out: mini-transaction */
+    trx_undo_ptr_t *redo_rseg_undo_ptr, /*!< in/out: Set trx
+                                        serialisation number in
+                                        referred undo rseg. */
+    trx_undo_ptr_t *temp_rseg_undo_ptr) /*!< in/out: Set trx
+                                        serialisation number in
+                                        referred undo rseg. */
+{
+  trx_rseg_t *redo_rseg = nullptr;
+  trx_rseg_t *temp_rseg = nullptr;
+  //trx_id_t new_id;
+
+  if (redo_rseg_undo_ptr != nullptr) {
+    ut_ad(mutex_own(&redo_rseg_undo_ptr->rseg->mutex));
+    redo_rseg = redo_rseg_undo_ptr->rseg;
+  }
+
+  if (temp_rseg_undo_ptr != nullptr) {
+    ut_ad(mutex_own(&temp_rseg_undo_ptr->rseg->mutex));
+    temp_rseg = temp_rseg_undo_ptr->rseg;
+  }
+
+  if ((redo_rseg != nullptr && redo_rseg->last_page_no == FIL_NULL) ||
+      (temp_rseg != nullptr && temp_rseg->last_page_no == FIL_NULL)) {
+    /* NOTE: here we MUST acquire the mutex to protect the purge queue before
+    invoking assign_new_trx_no, otherwise it might be possible to push a
+    segment with smaller trx_no than last time popped. */
+    mutex_enter(&purge_sys->pq_mutex);
+  }
+
+  trx_sys->assign_new_trx_no(trx);
+  //new_id = trx->no;
+
+  /* If the rollack segment is not empty then the
+  new trx_t::no can't be less than any trx_t::no
+  already in the rollback segment. User threads only
+  produce events when a rollback segment is empty. */
+  if ((redo_rseg != nullptr && redo_rseg->last_page_no == FIL_NULL) ||
+      (temp_rseg != nullptr && temp_rseg->last_page_no == FIL_NULL)) {
+    TrxUndoRsegs elem(trx->no);
+
+    if (redo_rseg != nullptr && redo_rseg->last_page_no == FIL_NULL) {
+      elem.push_back(redo_rseg);
+    }
+
+    if (temp_rseg != nullptr && temp_rseg->last_page_no == FIL_NULL) {
+      elem.push_back(temp_rseg);
+    }
+
+    purge_sys->purge_queue->push(elem);
+    mutex_exit(&purge_sys->pq_mutex);
+  }
+
+  /* new_id is a special trx id created for r/w trx which has update
+  undo. Such trx id is used by purge and the trx itself has no activity
+  so it's "effectived" committed right away. We need explicitly log it
+  in redo log, otherwise replica nodes would think this trx remains
+  active for ever. */
+  //mlog_log_transaction_end(mtr, new_id);
+}
+
+/** Assign the transaction its history serialisation number and write the
+ update UNDO log record to the assigned rollback segment.
+ @return true if a serialisation log was written */
+static void trx_write_serialisation_history(
+    trx_t *trx, /*!< in/out: transaction */
+    mtr_t *mtr) /*!< in/out: mini-transaction */
+{
+  /* Change the undo log segment states from TRX_UNDO_ACTIVE to some
+  other state: these modifications to the file data structure define
+  the transaction as committed in the file based domain, at the
+  serialization point of the log sequence number lsn obtained below. */
+
+  /* We have to hold the rseg mutex because update log headers have
+  to be put to the history list in the (serialisation) order of the
+  UNDO trx number. This is required for the purge in-memory data
+  structures too. */
+
+  bool own_redo_rseg_mutex = false;
+  bool own_temp_rseg_mutex = false;
+
+  /* Get rollback segment mutex. */
+  if (trx->rsegs.m_redo.rseg != nullptr && trx_is_redo_rseg_updated(trx)) {
+    mutex_enter(&trx->rsegs.m_redo.rseg->mutex);
+    own_redo_rseg_mutex = true;
+  }
+
+  mtr_t temp_mtr;
+
+  if (trx->rsegs.m_noredo.rseg != nullptr && trx_is_temp_rseg_updated(trx)) {
+    mutex_enter(&trx->rsegs.m_noredo.rseg->mutex);
+    own_temp_rseg_mutex = true;
+    mtr_start(&temp_mtr);
+    temp_mtr.set_log_mode(MTR_LOG_NO_REDO);
+  }
+
+  /* If transaction involves insert then truncate undo logs. */
+  if (trx->rsegs.m_redo.insert_undo != nullptr) {
+    trx_undo_set_state_at_finish(trx->rsegs.m_redo.insert_undo, mtr);
+  }
+
+  if (trx->rsegs.m_noredo.insert_undo != nullptr) {
+    trx_undo_set_state_at_finish(trx->rsegs.m_noredo.insert_undo, &temp_mtr);
+  }
+
+  /* If transaction involves update then add rollback segments
+  to purge queue. */
+  if (trx->rsegs.m_redo.update_undo != nullptr ||
+      trx->rsegs.m_noredo.update_undo != nullptr) {
+    /* Assign the transaction serialisation number and add these
+    rollback segments to purge trx-no sorted priority queue
+    if this is the first UNDO log being written to assigned
+    rollback segments. */
+
+    trx_undo_ptr_t *redo_rseg_undo_ptr =
+        trx->rsegs.m_redo.update_undo != nullptr ? &trx->rsegs.m_redo : nullptr;
+
+    trx_undo_ptr_t *temp_rseg_undo_ptr =
+        trx->rsegs.m_noredo.update_undo != nullptr ? &trx->rsegs.m_noredo
+                                                   : nullptr;
+
+    /* Will set trx->no and will add rseg to purge queue. */
+    trx_serialise(trx, mtr, redo_rseg_undo_ptr, temp_rseg_undo_ptr);
+
+    /* It is not necessary to obtain trx->undo_mutex here because
+    only a single OS thread is allowed to do the transaction commit
+    for this transaction. */
+    if (trx->rsegs.m_redo.update_undo != nullptr) {
+      page_t *undo_hdr_page;
+
+      undo_hdr_page =
+          trx_undo_set_state_at_finish(trx->rsegs.m_redo.update_undo, mtr);
+
+      /* Delay update of rseg_history_len if we plan to add
+      non-redo update_undo too. This is to avoid immediate
+      invocation of purge as we need to club these 2 segments
+      with same trx-no as single unit. */
+      bool update_rseg_len = !(trx->rsegs.m_noredo.update_undo != nullptr);
+
+      /* Set flag if GTID information need to persist. */
+      auto undo_ptr = &trx->rsegs.m_redo;
+      trx_undo_gtid_set(trx, undo_ptr->update_undo);
+
+      trx_undo_update_cleanup(trx, undo_ptr, undo_hdr_page, update_rseg_len,
+                              (update_rseg_len ? 1 : 0), mtr);
+    }
+
+    DBUG_EXECUTE_IF("ib_trx_crash_during_commit", DBUG_SUICIDE(););
+
+    if (trx->rsegs.m_noredo.update_undo != nullptr) {
+      page_t *undo_hdr_page;
+
+      undo_hdr_page = trx_undo_set_state_at_finish(
+          trx->rsegs.m_noredo.update_undo, &temp_mtr);
+
+      ulint n_added_logs = (redo_rseg_undo_ptr != nullptr) ? 2 : 1;
+
+      trx_undo_update_cleanup(trx, &trx->rsegs.m_noredo, undo_hdr_page, true,
+                              n_added_logs, &temp_mtr);
+    }
+  }
+
+  if (own_redo_rseg_mutex) {
+    mutex_exit(&trx->rsegs.m_redo.rseg->mutex);
+    own_redo_rseg_mutex = false;
+  }
+
+  if (own_temp_rseg_mutex) {
+    mutex_exit(&trx->rsegs.m_noredo.rseg->mutex);
+    own_temp_rseg_mutex = false;
+    mtr_commit(&temp_mtr);
+  }
+
+  MONITOR_INC(MONITOR_TRX_COMMIT_UNDO);
+
+  /* Update the latest MySQL binlog name and offset information
+  in trx sys header only if MySQL binary logging is on and clone
+  is has ensured commit order at final stage. */
+  if (Clone_handler::need_commit_order()) {  // NOLINT
+    trx_sys_update_mysql_binlog_offset(trx, mtr);
+  }
+}
+#else
 /** Set the transaction serialisation number.
  @return true if the transaction number was added to the serialisation_list. */
 static bool trx_serialisation_number_get(
@@ -1546,6 +1950,7 @@ static bool trx_write_serialisation_history(
 
   return (serialised);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /********************************************************************
 Finalize a transaction containing updates for a FTS table. */
@@ -1687,10 +2092,239 @@ static void trx_update_mod_tables_timestamp(trx_t *trx) /*!< in: transaction */
   trx->mod_tables.clear();
 }
 
-/**
-Erase the transaction from running transaction lists and serialization
-list. Active RW transaction list of a MVCC snapshot(ReadView::prepare)
-won't include this transaction after this call. All implicit locks are
+#ifdef HAVE_LOCK_FREE_READVIEW
+static void trx_release_impl_and_expl_locks(trx_t *trx, bool atShutdown) {
+  check_trx_state(trx);
+  ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE) ||
+        trx_state_eq(trx, TRX_STATE_PREPARED));
+
+  /* Check and get GTID to be persisted. Do it outside trx_sys mutex. */
+  Gtid_desc gtid_desc;
+  auto &gtid_persistor = clone_sys->get_gtid_persistor();
+  gtid_persistor.get_gtid_info(trx, gtid_desc);
+
+  if (trx->id > 0) {
+    /* For consistent snapshot, we need to remove current
+    transaction from running transaction id list for mvcc
+    before doing commit and releasing locks. */
+    if (!atShutdown) {
+      trx_sys->deregister_rw(trx);
+    }
+
+    /* Add GTID to be persisted to disk table. It must be done ...
+    1.After the transaction is marked committed in undo. Otherwise
+      GTID might get committed before the transaction commit on disk.
+    2.Before it is removed from serialization list. Otherwise the transaction
+      undo could get purged before persisting GTID on disk table. */
+    if (gtid_desc.m_is_set) {
+      auto &gtid_persistor = clone_sys->get_gtid_persistor();
+      gtid_persistor.add(gtid_desc);
+    }
+  }
+
+  trx_mutex_enter(trx);
+  /* Please consider this particular point in time as the moment the trx's
+  implicit locks become released.
+  This change is protected by both trx_sys->mutex and trx->mutex.
+  Therefore, there are two secure ways to check if the trx still can hold
+  implicit locks:
+  (1) if you only know id of the trx, then you can obtain trx_sys->mutex and
+      check if trx is still in rw_trx_set. This works, because the call to
+      trx_erase_list() which removes trx from this list several lines above is
+      also protected by trx_sys->mutex. We use this approach in
+      lock_rec_convert_impl_to_expl() by using trx_rw_is_active()
+  (2) if you have pointer to trx, and you know it is safe to access (say, you
+      hold reference to this trx which prevents it from being freed) then you
+      can obtain trx->mutex and check if trx->state is equal to
+      TRX_STATE_COMMITTED_IN_MEMORY. We use this approach in
+      lock_rec_convert_impl_to_expl_for_trx() when deciding for the final time
+      if we really want to create explicit lock on behalf of implicit lock
+      holder. */
+  trx->state = TRX_STATE_COMMITTED_IN_MEMORY;
+  trx_mutex_exit(trx);
+
+  lock_trx_release_locks(trx);
+}
+
+/** Commits a transaction in memory. */
+static void trx_commit_in_memory(
+    trx_t *trx,       /*!< in/out: transaction */
+    const mtr_t *mtr) /*!< in: mini-transaction of
+                      trx_write_serialisation_history(), or NULL if
+                      the transaction did not modify anything */
+{
+  trx->must_flush_log_later = false;
+  trx->ddl_must_flush = false;
+  trx->read_view.close();
+
+  if (trx_is_autocommit_non_locking(trx)) {
+    ut_ad(trx->id == 0);
+    ut_ad(trx->read_only);
+    ut_a(!trx->is_recovered);
+    ut_ad(trx->rsegs.m_redo.rseg == nullptr);
+
+    /* Note: We are asserting without holding the lock mutex. But
+    that is OK because this transaction is not waiting and cannot
+    be rolled back and no new locks can (or should not) be added
+    because it is flagged as a non-locking read-only transaction. */
+
+    ut_a(UT_LIST_GET_LEN(trx->lock.trx_locks) == 0);
+
+    /* This state change is not protected by any mutex, therefore
+    there is an inherent race here around state transition during
+    printouts. We ignore this race for the sake of efficiency.
+    However, the trx_sys_t::mutex will protect the trx_t instance
+    and it cannot be removed from the mysql_trx_list and freed
+    without first acquiring the trx_sys_t::mutex. */
+
+    ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE));
+
+    MONITOR_INC(MONITOR_TRX_NL_RO_COMMIT);
+
+    /* AC-NL-RO transactions can't be rolled back asynchronously. */
+    ut_ad(!trx->abort);
+    ut_ad(!(trx->in_innodb & (TRX_FORCE_ROLLBACK | TRX_FORCE_ROLLBACK_ASYNC)));
+
+    trx->state = TRX_STATE_NOT_STARTED;
+
+  } else {
+    trx_release_impl_and_expl_locks(trx);
+    DEBUG_SYNC_C("after_remove_serial_list_release_locks");
+
+    /* Remove the transaction from the list of active
+    transactions now that it no longer holds any user locks. */
+
+    ut_ad(trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY));
+    DEBUG_SYNC_C("after_trx_committed_in_memory");
+
+    if (trx->read_only || trx->rsegs.m_redo.rseg == nullptr) {
+      MONITOR_INC(MONITOR_TRX_RO_COMMIT);
+    } else {
+      ut_ad(trx->id > 0);
+      MONITOR_INC(MONITOR_TRX_RW_COMMIT);
+    }
+  }
+
+  if (trx->rsegs.m_redo.rseg != nullptr) {
+    trx_rseg_t *rseg = trx->rsegs.m_redo.rseg;
+    ut_ad(rseg->trx_ref_count > 0);
+
+    /* Multiple transactions can simultaneously decrement
+    the atomic counter. */
+    rseg->trx_ref_count--;
+  }
+
+  /* Reset flag that SE persists GTID. */
+  auto &gtid_persistor = clone_sys->get_gtid_persistor();
+  gtid_persistor.set_persist_gtid(trx, false);
+
+  if (mtr != nullptr) {
+    if (trx->rsegs.m_redo.insert_undo != nullptr) {
+      trx_undo_insert_cleanup(&trx->rsegs.m_redo, false);
+    }
+
+    if (trx->rsegs.m_noredo.insert_undo != nullptr) {
+      trx_undo_insert_cleanup(&trx->rsegs.m_noredo, true);
+    }
+
+    /* NOTE that we could possibly make a group commit more
+    efficient here: call os_thread_yield here to allow also other
+    trxs to come to commit! */
+
+    /*-------------------------------------*/
+
+    /* Depending on the my.cnf options, we may now write the log
+    buffer to the log files, making the transaction durable if
+    the OS does not crash. We may also flush the log files to
+    disk, making the transaction durable also at an OS crash or a
+    power outage.
+
+    The idea in InnoDB's group commit is that a group of
+    transactions gather behind a trx doing a physical disk write
+    to log files, and when that physical write has been completed,
+    one of those transactions does a write which commits the whole
+    group. Note that this group commit will only bring benefit if
+    there are > 2 users in the database. Then at least 2 users can
+    gather behind one doing the physical log write to disk.
+
+    If we are calling trx_commit() under prepare_commit_mutex, we
+    will delay possible log write and flush to a separate function
+    trx_commit_complete_for_mysql(), which is only called when the
+    thread has released the mutex. This is to make the
+    group commit algorithm to work. Otherwise, the prepare_commit
+    mutex would serialize all commits and prevent a group of
+    transactions from gathering. */
+
+    lsn_t lsn = mtr->commit_lsn();
+
+    if (lsn == 0) {
+      /* Nothing to be done. */
+    } else if (trx->flush_log_later) {
+      /* Do nothing yet */
+      trx->must_flush_log_later = true;
+
+      /* Remember current ddl_operation, because trx_init()
+      later will set ddl_operation to false. And the final
+      flush is even later. */
+      trx->ddl_must_flush = trx->ddl_operation;
+    } else if ((srv_flush_log_at_trx_commit == 0 ||
+                thd_requested_durability(trx->mysql_thd) ==
+                    HA_IGNORE_DURABILITY) &&
+               (!trx->ddl_operation)) {
+      /* Do nothing */
+    } else {
+      trx_flush_log_if_needed(lsn, trx);
+    }
+
+    trx->commit_lsn = lsn;
+
+    /* Tell server some activity has happened, since the trx
+    does changes something. Background utility threads like
+    master thread, purge thread or page_cleaner thread might
+    have some work to do. */
+    srv_active_wake_master_thread();
+  }
+
+  /* Free all savepoints, starting from the first. */
+  trx_named_savept_t *savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
+
+  trx_roll_savepoints_free(trx, savep);
+
+  if (trx->fts_trx != nullptr) {
+    trx_finalize_for_fts(trx, trx->undo_no != 0);
+  }
+
+  trx_mutex_enter(trx);
+  trx->dict_operation = TRX_DICT_OP_NONE;
+
+  /* Because we can rollback transactions asynchronously, we change
+  the state at the last step. trx_t::abort cannot change once commit
+  or rollback has started because we will have released the locks by
+  the time we get here. */
+
+  if (trx->abort) {
+    trx->abort = false;
+    trx->state = TRX_STATE_FORCED_ROLLBACK;
+  } else {
+    trx->state = TRX_STATE_NOT_STARTED;
+  }
+
+  /* trx->in_mysql_trx_list would hold between
+  trx_allocate_for_mysql() and trx_free_for_mysql(). It does not
+  hold for recovered transactions or system transactions. */
+  assert_trx_is_free(trx);
+
+  trx_init(trx);
+
+  trx_mutex_exit(trx);
+
+  ut_a(trx->error_state == DB_SUCCESS);
+}
+#else
+/**
+Erase the transaction from running transaction lists and serialization
+list. Active RW transaction list of a MVCC snapshot(ReadView::prepare)
+won't include this transaction after this call. All implicit locks are
 also released by this call as trx is removed from rw_trx_list.
 @param[in]	trx		Transaction to erase, must have an ID > 0
 @param[in]	serialised	true if serialisation log was written
@@ -1980,6 +2614,7 @@ written */
 
   ut_a(trx->error_state == DB_SUCCESS);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Commits a transaction and a mini-transaction. */
 void trx_commit_low(
@@ -2013,12 +2648,18 @@ void trx_commit_low(
     }
   }
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   bool serialised;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   if (mtr != NULL) {
     mtr->set_sync();
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_write_serialisation_history(trx, mtr);
+#else
     serialised = trx_write_serialisation_history(trx, mtr);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
     /* The following call commits the mini-transaction, making the
     whole transaction committed in the file-based world, at this
@@ -2056,7 +2697,9 @@ void trx_commit_low(
     /*--------------*/
 
   } else {
+#ifndef HAVE_LOCK_FREE_READVIEW
     serialised = false;
+#endif /* HAVE_LOCK_FREE_READVIEW */
   }
 #ifdef UNIV_DEBUG
   /* In case of this function is called from a stack executing
@@ -2072,7 +2715,11 @@ void trx_commit_low(
   }
 #endif
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_commit_in_memory(trx, mtr);
+#else
   trx_commit_in_memory(trx, mtr, serialised);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 }
 
 /** Commits a transaction. */
@@ -2098,6 +2745,7 @@ void trx_commit(trx_t *trx) /*!< in/out: transaction */
   trx_commit_low(trx, mtr);
 }
 
+#ifndef HAVE_LOCK_FREE_READVIEW
 /** Cleans up a transaction at database startup. The cleanup is needed if
  the transaction already got to the middle of a commit when the database
  crashed, and we cannot roll it back. */
@@ -2155,6 +2803,7 @@ ReadView *trx_assign_read_view(trx_t *trx) /*!< in/out: active transaction */
 
   return (trx->read_view);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Prepares a transaction for commit/rollback. */
 void trx_commit_or_rollback_prepare(trx_t *trx) /*!< in/out: transaction */
@@ -2377,7 +3026,9 @@ void trx_print_low(FILE *f,
   ibool newline;
   const char *op_info;
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(trx_sys_mutex_own());
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   fprintf(f, "TRANSACTION " TRX_ID_FMT, trx_get_id_for_print(trx));
 
@@ -2494,7 +3145,9 @@ void trx_print_latched(
                          or 0 to use the default max length */
 {
   ut_ad(lock_mutex_own());
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(trx_sys_mutex_own());
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   trx_print_low(f, trx, max_query_len, lock_number_of_rows_locked(&trx->lock),
                 UT_LIST_GET_LEN(trx->lock.trx_locks),
@@ -2518,11 +3171,15 @@ void trx_print(FILE *f,             /*!< in: output stream */
   heap_size = mem_heap_get_size(trx->lock.lock_heap);
   lock_mutex_exit();
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   mutex_enter(&trx_sys->mutex);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   trx_print_low(f, trx, max_query_len, n_rec_locks, n_trx_locks, heap_size);
 
+#ifndef HAVE_LOCK_FREE_READVIEW
   mutex_exit(&trx_sys->mutex);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 }
 
 #ifdef UNIV_DEBUG
@@ -2531,7 +3188,9 @@ void trx_print(FILE *f,             /*!< in: output stream */
  @return true if started */
 ibool trx_assert_started(const trx_t *trx) /*!< in: transaction */
 {
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(trx_sys_mutex_own());
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
   /* Non-locking autocommits should not hold any locks and this
   function is only called from the locking code. */
@@ -2681,6 +3340,15 @@ static void trx_prepare(trx_t *trx) /*!< in/out: transaction */
 
   /*--------------------------------------*/
   ut_a(trx->state == TRX_STATE_ACTIVE);
+#ifdef HAVE_LOCK_FREE_READVIEW
+  trx_mutex_enter(trx);
+  trx->state = TRX_STATE_PREPARED;
+  /* Add GTID to be persisted to disk table, if needed. */
+  if (gtid_desc.m_is_set) {
+    gtid_persistor.add(gtid_desc);
+  }
+  trx_mutex_exit(trx);
+#else
   trx_sys_mutex_enter();
   trx->state = TRX_STATE_PREPARED;
   trx_sys->n_prepared_trx++;
@@ -2689,6 +3357,7 @@ static void trx_prepare(trx_t *trx) /*!< in/out: transaction */
     gtid_persistor.add(gtid_desc);
   }
   trx_sys_mutex_exit();
+#endif /* HAVE_LOCK_FREE_READVIEW */
   /*--------------------------------------*/
   DEBUG_SYNC_C("trx_prepare_has_changed_state");
 
@@ -2830,6 +3499,123 @@ static bool get_info_about_prepared_transaction(XA_recover_txn *txn_list,
   return false;
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+struct trx_recover_for_mysql_callback_arg {
+  XA_recover_txn *txn_list;
+  MEM_ROOT *mem_root;
+  ulint len;
+  ulint count;
+};
+
+static bool trx_recover_for_mysql_callback(
+    rw_trx_hash_element_t *element, trx_recover_for_mysql_callback_arg *arg) {
+  mutex_enter(&element->mutex);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    /* The state of a read-write transaction cannot change
+    from or to NOT_STARTED while we are holding the
+    trx_sys->mutex. It may change to PREPARED, but not if
+    trx->is_recovered. It may also change to COMMITTED. */
+    if (trx_state_eq(trx, TRX_STATE_PREPARED)) {
+      if (get_info_about_prepared_transaction(&arg->txn_list[arg->count], trx,
+                                              arg->mem_root)) {
+        mutex_exit(&element->mutex);
+        return true;
+      }
+
+      if (arg->count == 0) {
+        ib::info(ER_IB_MSG_1207) << "Starting recovery for"
+                                    " XA transactions...";
+      }
+
+      ib::info(ER_IB_MSG_1208) << "Transaction " << trx_get_id_for_print(trx)
+                               << " in prepared state after recovery";
+
+      ib::info(ER_IB_MSG_1209)
+          << "Transaction contains changes to " << trx->undo_no << " rows";
+
+      arg->txn_list[arg->count++].id = *trx->xid;
+    }
+  }
+  mutex_exit(&element->mutex);
+  return (arg->count == arg->len);
+}
+
+/** This function is used to find number of prepared transactions and
+ their transaction objects for a recovery.
+ @return number of prepared transactions stored in xid_list */
+int trx_recover_for_mysql(
+    XA_recover_txn *txn_list, /*!< in/out: prepared transactions */
+    ulint len,                /*!< in: number of slots in xid_list */
+    MEM_ROOT *mem_root)       /*!< in: memory for table names */
+{
+  ut_ad(txn_list != nullptr);
+  ut_ad(len > 0);
+
+  trx_recover_for_mysql_callback_arg arg = {txn_list, mem_root, len, 0U};
+
+  /* Fill txb_list with PREPARED transactions. */
+  trx_sys->rw_trx_hash.iterate_no_dups(
+      reinterpret_cast<lf_hash_walk_func *>(trx_recover_for_mysql_callback),
+      &arg);
+
+  if (arg.count > 0) {
+    ib::info(ER_IB_MSG_1210) << arg.count
+                             << " transactions in prepared state"
+                                " after recovery";
+  }
+
+  return (static_cast<int>(arg.count));
+}
+
+struct trx_get_trx_by_xid_callback_arg {
+  const XID *xid;
+  trx_t *trx;
+};
+
+static bool trx_get_trx_by_xid_callback(rw_trx_hash_element_t *element,
+                                        trx_get_trx_by_xid_callback_arg *arg) {
+  bool found = false;
+
+  mutex_enter(&element->mutex);
+  trx_t *trx = element->trx;
+  if (trx != nullptr) {
+    /* Compare two X/Open XA transaction id's: their
+    length should be the same and binary comparison
+    of gtrid_length+bqual_length bytes should be
+    the same */
+
+    if (trx->is_recovered && trx_state_eq(trx, TRX_STATE_PREPARED) &&
+        arg->xid->eq(trx->xid)) {
+      /* Invalidate the XID, so that subsequent calls
+      will not find it. */
+      trx->xid->reset();
+      arg->trx = trx;
+      found = true;
+    }
+  }
+  mutex_exit(&element->mutex);
+
+  return found;
+}
+
+/** This function is used to find one X/Open XA distributed transaction
+ which is in the prepared state
+ @return trx or NULL; on match, the trx->xid will be invalidated;
+ note that the trx may have been committed, unless the caller is
+ holding lock_sys->mutex */
+trx_t *trx_get_trx_by_xid(
+    const XID *xid) /*!< in: X/Open XA transaction identifier */
+{
+  trx_get_trx_by_xid_callback_arg arg = {xid, nullptr};
+  if (xid != nullptr) {
+    trx_sys->rw_trx_hash.iterate(
+        reinterpret_cast<lf_hash_walk_func *>(trx_get_trx_by_xid_callback),
+        &arg);
+  }
+  return arg.trx;
+}
+#else
 /** This function is used to find number of prepared transactions and
  their transaction objects for a recovery.
  @return number of prepared transactions stored in xid_list */
@@ -2949,6 +3735,7 @@ trx_t *trx_get_trx_by_xid(
 
   return (trx);
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 /** Starts the transaction if it is not yet started. */
 void trx_start_if_not_started_xa_low(
@@ -2963,10 +3750,17 @@ void trx_start_if_not_started_xa_low(
 
     case TRX_STATE_ACTIVE:
       if (trx->id == 0 && read_write) {
-        /* If the transaction is tagged as read-only then
-        it can only write to temp tables and for such
-        transactions we don't want to move them to the
-        trx_sys_t::rw_trx_list. */
+#ifdef HAVE_LOCK_FREE_READVIEW
+      /* If the transaction is tagged as read-only then
+      it can only write to temp tables and for such
+      transactions we don't want to move them to the
+      trx_sys_t::rw_trx_hash. */
+#else
+      /* If the transaction is tagged as read-only then
+      it can only write to temp tables and for such
+      transactions we don't want to move them to the
+      trx_sys_t::rw_trx_list. */
+#endif /* HAVE_LOCK_FREE_READVIEW */
         if (!trx->read_only) {
           trx_set_rw_mode(trx);
         } else if (!srv_read_only_mode) {
@@ -3044,7 +3838,9 @@ void trx_start_internal_read_only_low(trx_t *trx) {
 void trx_set_rw_mode(trx_t *trx) /*!< in/out: transaction that is RW */
 {
   ut_ad(trx->rsegs.m_redo.rseg == 0);
+#ifndef HAVE_LOCK_FREE_READVIEW
   ut_ad(!trx->in_rw_trx_list);
+#endif /* HAVE_LOCK_FREE_READVIEW */
   ut_ad(!trx_is_autocommit_non_locking(trx));
   ut_ad(!trx->read_only);
 
@@ -3063,6 +3859,20 @@ void trx_set_rw_mode(trx_t *trx) /*!< in/out: transaction that is RW */
 
   ut_ad(trx->rsegs.m_redo.rseg != 0);
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+    trx_sys->register_rw(trx);
+
+  /* So that we can see our own changes. */
+  if (trx->read_view.is_open()) {
+    trx->read_view.set_creator_trx_id(trx->id);
+  }
+
+#ifdef UNIV_DEBUG
+  if (trx->id > trx_sys->rw_max_trx_no) {
+    trx_sys->rw_max_trx_no = trx->id;
+  }
+#endif /* UNIV_DEBUG */
+#else
   mutex_enter(&trx_sys->mutex);
 
   ut_ad(trx->id == 0);
@@ -3082,6 +3892,7 @@ void trx_set_rw_mode(trx_t *trx) /*!< in/out: transaction that is RW */
   ut_d(trx->in_rw_trx_list = true);
 
   mutex_exit(&trx_sys->mutex);
+#endif /* HAVE_LOCK_FREE_READVIEW */
 }
 
 void trx_kill_blocking(trx_t *trx) {
diff --git a/storage/innobase/trx/trx0undo.cc b/storage/innobase/trx/trx0undo.cc
index 7503d11..f963972 100644
--- a/storage/innobase/trx/trx0undo.cc
+++ b/storage/innobase/trx/trx0undo.cc
@@ -1885,6 +1885,71 @@ void trx_undo_insert_cleanup(trx_undo_ptr_t *undo_ptr, bool noredo) {
   mutex_exit(&(rseg->mutex));
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+/** At shutdown, frees the undo logs of a transaction. */
+void trx_undo_free_at_shutdown(trx_t *trx) {
+  if (trx->rsegs.m_redo.update_undo != nullptr) {
+    switch (trx->rsegs.m_redo.update_undo->state) {
+      case TRX_UNDO_PREPARED:
+        break;
+      case TRX_UNDO_CACHED:
+      case TRX_UNDO_TO_FREE:
+      case TRX_UNDO_TO_PURGE:
+      case TRX_UNDO_ACTIVE:
+        ut_ad(trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY));
+        break;
+      default:
+        ut_error;
+    }
+
+    UT_LIST_REMOVE(trx->rsegs.m_redo.rseg->update_undo_list,
+                   trx->rsegs.m_redo.update_undo);
+    trx_undo_mem_free(trx->rsegs.m_redo.update_undo);
+
+    trx->rsegs.m_redo.update_undo = nullptr;
+  }
+
+  if (trx->rsegs.m_redo.insert_undo != nullptr) {
+    switch (trx->rsegs.m_redo.insert_undo->state) {
+      case TRX_UNDO_PREPARED:
+        break;
+      case TRX_UNDO_CACHED:
+      case TRX_UNDO_TO_FREE:
+      case TRX_UNDO_TO_PURGE:
+      case TRX_UNDO_ACTIVE:
+        ut_ad(trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY));
+        break;
+      default:
+        ut_error;
+    }
+
+    UT_LIST_REMOVE(trx->rsegs.m_redo.rseg->insert_undo_list,
+                   trx->rsegs.m_redo.insert_undo);
+    trx_undo_mem_free(trx->rsegs.m_redo.insert_undo);
+
+    trx->rsegs.m_redo.insert_undo = nullptr;
+  }
+
+  if (trx->rsegs.m_noredo.update_undo != nullptr) {
+    ut_a(trx->rsegs.m_noredo.update_undo->state == TRX_UNDO_PREPARED);
+
+    UT_LIST_REMOVE(trx->rsegs.m_noredo.rseg->update_undo_list,
+                   trx->rsegs.m_noredo.update_undo);
+    trx_undo_mem_free(trx->rsegs.m_noredo.update_undo);
+
+    trx->rsegs.m_noredo.update_undo = nullptr;
+  }
+  if (trx->rsegs.m_noredo.insert_undo != nullptr) {
+    ut_a(trx->rsegs.m_noredo.insert_undo->state == TRX_UNDO_PREPARED);
+
+    UT_LIST_REMOVE(trx->rsegs.m_noredo.rseg->insert_undo_list,
+                   trx->rsegs.m_noredo.insert_undo);
+    trx_undo_mem_free(trx->rsegs.m_noredo.insert_undo);
+
+    trx->rsegs.m_noredo.insert_undo = nullptr;
+  }
+}
+#else
 /** At shutdown, frees the undo logs of a PREPARED transaction. */
 void trx_undo_free_prepared(trx_t *trx) /*!< in/out: PREPARED transaction */
 {
@@ -1927,6 +1992,7 @@ void trx_undo_free_prepared(trx_t *trx) /*!< in/out: PREPARED transaction */
     trx->rsegs.m_noredo.insert_undo = NULL;
   }
 }
+#endif /* HAVE_LOCK_FREE_READVIEW */
 
 bool trx_undo_truncate_tablespace(undo::Tablespace *marked_space) {
 #ifdef UNIV_DEBUG
diff --git a/storage/innobase/ut/ut0new.cc b/storage/innobase/ut/ut0new.cc
index 46ce424..815803f 100644
--- a/storage/innobase/ut/ut0new.cc
+++ b/storage/innobase/ut/ut0new.cc
@@ -54,7 +54,9 @@ PSI_memory_key mem_key_partitioning;
 PSI_memory_key mem_key_row_log_buf;
 PSI_memory_key mem_key_row_merge_sort;
 PSI_memory_key mem_key_std;
+#ifndef HAVE_LOCK_FREE_READVIEW
 PSI_memory_key mem_key_trx_sys_t_rw_trx_ids;
+#endif /* HAVE_LOCK_FREE_READVIEW */
 PSI_memory_key mem_key_undo_spaces;
 PSI_memory_key mem_key_ut_lock_free_hash_t;
 /* Please obey alphabetical order in the definitions above. */
@@ -94,8 +96,10 @@ static PSI_memory_info pfs_info[] = {
     {&mem_key_row_log_buf, "row_log_buf", 0, 0, PSI_DOCUMENT_ME},
     {&mem_key_row_merge_sort, "row_merge_sort", 0, 0, PSI_DOCUMENT_ME},
     {&mem_key_std, "std", 0, 0, PSI_DOCUMENT_ME},
+#ifndef HAVE_LOCK_FREE_READVIEW
     {&mem_key_trx_sys_t_rw_trx_ids, "trx_sys_t::rw_trx_ids", 0, 0,
      PSI_DOCUMENT_ME},
+#endif /* HAVE_LOCK_FREE_READVIEW */
     {&mem_key_undo_spaces, "undo::Tablespaces", 0, 0, PSI_DOCUMENT_ME},
     {&mem_key_ut_lock_free_hash_t, "ut_lock_free_hash_t", 0, 0,
      PSI_DOCUMENT_ME},
diff --git a/unittest/gunit/mysys_lf-t.cc b/unittest/gunit/mysys_lf-t.cc
index d658834..7a5a30d 100644
--- a/unittest/gunit/mysys_lf-t.cc
+++ b/unittest/gunit/mysys_lf-t.cc
@@ -122,10 +122,20 @@ extern "C" void *test_lf_alloc(void *arg) {
   return 0;
 }
 
+#ifdef HAVE_LOCK_FREE_READVIEW
+bool do_sum(void *num, void *acc) {
+  *static_cast<int *>(acc) += *static_cast<int *>(num);
+  return false;
+}
+#endif /* HAVE_LOCK_FREE_READVIEW */
 const int N_TLH = 1000;
 extern "C" void *test_lf_hash(void *arg) {
   int m = (*(int *)arg) / (2 * N_TLH);
+#ifdef HAVE_LOCK_FREE_READVIEW
+  int32 x, y, z, sum = 0, ins = 0, scans = 0;
+#else
   int32 x, y, z, sum = 0, ins = 0;
+#endif /* HAVE_LOCK_FREE_READVIEW */
   LF_PINS *pins;
 
   if (with_my_thread_init) my_thread_init();
@@ -142,6 +152,13 @@ extern "C" void *test_lf_hash(void *arg) {
         sum += z;
         ins++;
       }
+#ifdef HAVE_LOCK_FREE_READVIEW
+      else {
+        int unused = 0;
+        lf_hash_iterate(&lf_hash, pins, do_sum, &unused);
+        scans++;
+      }
+#endif /* HAVE_LOCK_FREE_READVIEW */
     }
     for (i = 0; i < N_TLH; i++) {
       y = (y * (m + i) + 0x87654321) & INT_MAX32;
-- 
1.8.3.1

