@@ -340,14 +340,6 @@ struct rw_trx_hash_element_t
340340
341341
342342 trx_id_t id; /* lf_hash_init() relies on this to be first in the struct */
343-
344- /* *
345- Transaction serialization number.
346-
347- Assigned shortly before the transaction is moved to COMMITTED_IN_MEMORY
348- state. Initially set to TRX_ID_MAX.
349- */
350- Atomic_counter<trx_id_t > no;
351343 trx_t *trx;
352344 srw_mutex mutex;
353345};
@@ -443,7 +435,6 @@ class rw_trx_hash_t
443435 ut_ad (element->trx == 0 );
444436 element->trx = trx;
445437 element->id = trx->id ;
446- element->no = TRX_ID_MAX ;
447438 trx->rw_trx_hash_element = element;
448439 }
449440
@@ -512,7 +503,6 @@ class rw_trx_hash_t
512503 if (element->trx )
513504 validate_element (element->trx );
514505 element->mutex .wr_unlock ();
515- ut_ad (element->id < element->no );
516506 return arg->action (element, arg->argument );
517507 }
518508#endif
@@ -849,6 +839,131 @@ class thread_safe_trx_ilist_t
849839 alignas (CPU_LEVEL1_DCACHE_LINESIZE ) ilist<trx_t > trx_list;
850840};
851841
842+ /* *
843+ Active read-write transaction identifiers and serialisation numbers container.
844+
845+ Unlike rw_trx_hash_t, which is optimized for direct lookup, this
846+ structure is optimized for compact storage and traversal of active
847+ transactions by MVCC read view construction.
848+
849+ The vector may contain empty slots corresponding to idle or read-only
850+ transactions that currently do not own an active read-write trx_id.
851+ Such slots are skipped during traversal.
852+ */
853+ class rw_trx_vector
854+ {
855+ struct rw_trx_id
856+ {
857+ Atomic_relaxed<trx_id_t > id{TRX_ID_MAX };
858+ Atomic_relaxed<trx_id_t > no{TRX_ID_MAX };
859+ };
860+ alignas (CPU_LEVEL1_DCACHE_LINESIZE )
861+ std::vector<rw_trx_id, ut_allocator<rw_trx_id>>
862+ ids{ut_allocator<rw_trx_id>(mem_key_trx_sys_t_rw_trx_ids)};
863+ alignas (CPU_LEVEL1_DCACHE_LINESIZE )
864+ std::vector<trx_t *, ut_allocator<trx_t *>>
865+ trxs{ut_allocator<trx_t *>(mem_key_trx_sys_t_rw_trx_ids)};
866+ alignas (CPU_LEVEL1_DCACHE_LINESIZE ) mutable srw_spin_lock_low latch;
867+
868+ public:
869+ template <typename Functor1, typename Functor2>
870+ trx_id_t assign_new_trx_no (const trx_t *trx, Functor1 get_new_trx_id,
871+ Functor2 refresh_rw_trx_hash_version) noexcept
872+ {
873+ trx_id_t no;
874+ latch.rd_lock ();
875+ ut_ad (trx->rw_trx_ids_slot < ids.size ());
876+ ut_ad (trxs[trx->rw_trx_ids_slot ] == trx);
877+ ut_ad (ids[trx->rw_trx_ids_slot ].id == trx->id );
878+ ut_ad (ids[trx->rw_trx_ids_slot ].no == TRX_ID_MAX );
879+ ids[trx->rw_trx_ids_slot ].no = no= get_new_trx_id ();
880+ refresh_rw_trx_hash_version ();
881+ latch.rd_unlock ();
882+ return no;
883+ }
884+ trx_id_t snapshot_ids (trx_ids_t &view_ids,
885+ const trx_id_t max_trx_id) const noexcept
886+ {
887+ trx_id_t min_trx_no{max_trx_id};
888+ view_ids.clear ();
889+ latch.rd_lock ();
890+ view_ids.reserve (ids.size ());
891+ for (const auto &it : ids)
892+ {
893+ trx_id_t id{it.id };
894+ if (id < max_trx_id)
895+ {
896+ view_ids.push_back (id);
897+ const trx_id_t no{it.no };
898+ if (no < min_trx_no)
899+ min_trx_no= no;
900+ }
901+ }
902+ latch.rd_unlock ();
903+ return min_trx_no;
904+ }
905+ template <typename Functor1, typename Functor2>
906+ void register_rw (const trx_t *trx, Functor1 get_new_trx_id,
907+ Functor2 refresh_rw_trx_hash_version) noexcept
908+ {
909+ latch.rd_lock ();
910+ ut_ad (trx->rw_trx_ids_slot < ids.size ());
911+ ut_ad (trxs[trx->rw_trx_ids_slot ] == trx);
912+ ut_ad (ids[trx->rw_trx_ids_slot ].id == TRX_ID_MAX );
913+ ut_ad (ids[trx->rw_trx_ids_slot ].no == TRX_ID_MAX );
914+ ids[trx->rw_trx_ids_slot ].id = get_new_trx_id ();
915+ refresh_rw_trx_hash_version ();
916+ latch.rd_unlock ();
917+ }
918+ void deregister_rw (const trx_t *trx) noexcept
919+ {
920+ latch.rd_lock ();
921+ ut_ad (trx->rw_trx_ids_slot < ids.size ());
922+ rw_trx_id &slot= ids[trx->rw_trx_ids_slot ];
923+ ut_ad (trxs[trx->rw_trx_ids_slot ] == trx);
924+ ut_ad (slot.id == trx->id );
925+ slot.id = TRX_ID_MAX ;
926+ slot.no = TRX_ID_MAX ;
927+ latch.rd_unlock ();
928+ }
929+ void register_trx (trx_t *trx) noexcept
930+ {
931+ ut_ad (trx->rw_trx_ids_slot == std::numeric_limits<uint32_t >::max ());
932+ latch.wr_lock ();
933+ trx->rw_trx_ids_slot = static_cast <uint32_t >(ids.size ());
934+ ids.emplace_back ();
935+ trxs.emplace_back (trx);
936+ latch.wr_unlock ();
937+ }
938+ void deregister_trx (trx_t *trx) noexcept
939+ {
940+ latch.wr_lock ();
941+ ut_ad (trx->rw_trx_ids_slot < ids.size ());
942+ ut_ad (trxs[trx->rw_trx_ids_slot ] == trx);
943+ if (trx->rw_trx_ids_slot + 1 < ids.size ())
944+ {
945+ trx_t *move_trx= trxs.back ();
946+ ids[trx->rw_trx_ids_slot ]= std::move (ids.back ());
947+ trxs[trx->rw_trx_ids_slot ]= std::move (trxs.back ());
948+ move_trx->rw_trx_ids_slot = trx->rw_trx_ids_slot ;
949+ }
950+ ids.pop_back ();
951+ trxs.pop_back ();
952+ latch.wr_unlock ();
953+ trx->rw_trx_ids_slot = std::numeric_limits<uint32_t >::max ();
954+ }
955+ void create () noexcept
956+ {
957+ ut_ad (ids.size () == 0 );
958+ latch.init ();
959+ }
960+ void destroy () noexcept
961+ {
962+ ut_ad (ids.size () == 0 );
963+ latch.destroy ();
964+ }
965+ };
966+
852967/* * The transaction system central memory data structure. */
853968class trx_sys_t
854969{
@@ -875,6 +990,15 @@ class trx_sys_t
875990
876991 /* * False if there is no undo log to purge or rollback */
877992 bool undo_log_nonempty;
993+
994+ /* *
995+ Collection of active read-write transaction identifiers and serialization
996+ numbers used for MVCC snapshot creation.
997+
998+ This complements rw_trx_hash with a traversal-friendly representation
999+ optimized for collecting active transaction ids.
1000+ */
1001+ rw_trx_vector rw_trx_ids;
8781002public:
8791003 /* * List of all transactions. */
8801004 thread_safe_trx_ilist_t trx_list;
@@ -1014,7 +1138,7 @@ class trx_sys_t
10141138 next call to trx_sys.get_new_trx_id()
10151139 */
10161140
1017- trx_id_t get_max_trx_id ()
1141+ trx_id_t get_max_trx_id () const noexcept
10181142 {
10191143 return m_max_trx_id;
10201144 }
@@ -1037,7 +1161,7 @@ class trx_sys_t
10371161 Allocates and assigns new transaction serialisation number.
10381162
10391163 There's a gap between m_max_trx_id increment and transaction serialisation
1040- number becoming visible through rw_trx_hash . While we're in this gap
1164+ number becoming visible through rw_trx_ids . While we're in this gap
10411165 concurrent thread may come and do MVCC snapshot without seeing allocated
10421166 but not yet assigned serialisation number. Then at some point purge thread
10431167 may clone this view. As a result it won't see newly allocated serialisation
@@ -1047,58 +1171,43 @@ class trx_sys_t
10471171 m_rw_trx_hash_version is intended to solve this problem. MVCC snapshot has
10481172 to wait until m_max_trx_id == m_rw_trx_hash_version, which effectively
10491173 means that all transaction serialisation numbers up to m_max_trx_id are
1050- available through rw_trx_hash .
1174+ available through rw_trx_ids .
10511175
10521176 We rely on refresh_rw_trx_hash_version() to issue RELEASE memory barrier so
1053- that m_rw_trx_hash_version increment happens after
1054- trx->rw_trx_hash_element->no becomes visible through rw_trx_hash .
1177+ that m_rw_trx_hash_version increment happens after transaction serialisation
1178+ number becomes visible through rw_trx_ids .
10551179
10561180 @param trx transaction
10571181 */
1058- void assign_new_trx_no (trx_t *trx)
1182+ trx_id_t assign_new_trx_no (trx_t *trx)
10591183 {
1060- trx->rw_trx_hash_element ->no = get_new_trx_id_no_refresh ();
1061- refresh_rw_trx_hash_version ();
1184+ return rw_trx_ids.assign_new_trx_no (trx,
1185+ [this ](){ return get_new_trx_id_no_refresh (); },
1186+ [this ](){ refresh_rw_trx_hash_version (); });
10621187 }
10631188
10641189
10651190 /* *
10661191 Takes MVCC snapshot.
10671192
1068- To reduce malloc probability we reserve rw_trx_hash.size() + 32 elements
1069- in ids.
1070-
10711193 For details about get_rw_trx_hash_version() != get_max_trx_id() spin
10721194 @sa register_rw() and @sa assign_new_trx_no().
10731195
10741196 We rely on get_rw_trx_hash_version() to issue ACQUIRE memory barrier so
1075- that loading of m_rw_trx_hash_version happens before accessing rw_trx_hash .
1197+ that loading of m_rw_trx_hash_version happens before accessing rw_trx_ids .
10761198
1077- To optimise snapshot creation rw_trx_hash.iterate() is being used instead
1078- of rw_trx_hash.iterate_no_dups(). It means that some transaction
1079- identifiers may appear multiple times in ids.
1080-
1081- @param[in,out] caller_trx used to get access to rw_trx_hash_pins
10821199 @param[out] ids array to store registered transaction identifiers
10831200 @param[out] max_trx_id variable to store m_max_trx_id value
1084- @param[out] mix_trx_no variable to store min(no) value
1201+
1202+ @return min(no)
10851203 */
10861204
1087- void snapshot_ids (trx_t *caller_trx, trx_ids_t *ids, trx_id_t *max_trx_id,
1088- trx_id_t *min_trx_no)
1205+ trx_id_t snapshot_ids (trx_ids_t &ids, trx_id_t &max_trx_id) const noexcept
10891206 {
1090- snapshot_ids_arg arg (ids);
1091-
1092- while ((arg.m_id = get_rw_trx_hash_version ()) != get_max_trx_id ())
1207+ while ((max_trx_id= get_rw_trx_hash_version ()) != get_max_trx_id ())
10931208 ut_delay (1 );
1094- arg.m_no = arg.m_id ;
1095-
1096- ids->clear ();
1097- ids->reserve (rw_trx_hash.size () + 32 );
1098- rw_trx_hash.iterate(caller_trx, copy_one_id, &arg);
10991209
1100- *max_trx_id= arg.m_id ;
1101- *min_trx_no= arg.m_no ;
1210+ return rw_trx_ids.snapshot_ids (ids, max_trx_id);
11021211 }
11031212
11041213
@@ -1149,7 +1258,7 @@ class trx_sys_t
11491258 Transaction becomes visible to MVCC.
11501259
11511260 There's a gap between m_max_trx_id increment and transaction becoming
1152- visible through rw_trx_hash . While we're in this gap concurrent thread may
1261+ visible through rw_trx_ids . While we're in this gap concurrent thread may
11531262 come and do MVCC snapshot. As a result concurrent read view will be able to
11541263 observe records owned by this transaction even before it was committed.
11551264
@@ -1165,21 +1274,32 @@ class trx_sys_t
11651274
11661275 void register_rw (trx_t *trx)
11671276 {
1168- trx->id = get_new_trx_id_no_refresh ();
1277+ rw_trx_ids.register_rw (trx,
1278+ [this , trx](){ return trx->id = get_new_trx_id_no_refresh (); },
1279+ [this ](){ refresh_rw_trx_hash_version (); });
11691280 rw_trx_hash.insert (trx);
1170- refresh_rw_trx_hash_version ();
1281+ }
1282+
1283+
1284+ void resurrect_rw (trx_t *trx)
1285+ {
1286+ rw_trx_ids.register_rw (trx, [trx](){ return trx->id ; }, [](){});
1287+ rw_trx_hash.insert (trx);
1288+ rw_trx_hash.put_pins (trx);
11711289 }
11721290
11731291
11741292 /* *
11751293 Deregisters read-write transaction.
11761294
1177- Transaction is removed from rw_trx_hash, which releases all implicit locks.
1178- MVCC snapshot won't see this transaction anymore.
1295+ After this call the transaction is no longer visible as active to MVCC read
1296+ views created subsequently, and all implicit locks held by the transaction
1297+ have been released.
11791298 */
11801299
1181- void deregister_rw (trx_t *trx)
1300+ void deregister_rw (trx_t *trx) noexcept
11821301 {
1302+ rw_trx_ids.deregister_rw (trx);
11831303 rw_trx_hash.erase (trx);
11841304 }
11851305
@@ -1204,6 +1324,7 @@ class trx_sys_t
12041324 void register_trx (trx_t *trx)
12051325 {
12061326 trx_list.push_front (*trx);
1327+ rw_trx_ids.register_trx (trx);
12071328 }
12081329
12091330
@@ -1214,6 +1335,7 @@ class trx_sys_t
12141335 */
12151336 void deregister_trx (trx_t *trx)
12161337 {
1338+ rw_trx_ids.deregister_trx (trx);
12171339 trx_list.remove (*trx);
12181340 }
12191341
@@ -1266,33 +1388,8 @@ class trx_sys_t
12661388private:
12671389 static my_bool find_same_or_older_callback (void *el, void *i) noexcept ;
12681390
1269-
1270- struct snapshot_ids_arg
1271- {
1272- snapshot_ids_arg (trx_ids_t *ids): m_ids(ids) {}
1273- trx_ids_t *m_ids;
1274- trx_id_t m_id;
1275- trx_id_t m_no;
1276- };
1277-
1278-
1279- static my_bool copy_one_id (void * el, void *a)
1280- {
1281- auto element= static_cast <const rw_trx_hash_element_t *>(el);
1282- auto arg= static_cast <snapshot_ids_arg*>(a);
1283- if (element->id < arg->m_id )
1284- {
1285- trx_id_t no= element->no ;
1286- arg->m_ids ->push_back (element->id );
1287- if (no < arg->m_no )
1288- arg->m_no = no;
1289- }
1290- return 0 ;
1291- }
1292-
1293-
12941391 /* * Getter for m_rw_trx_hash_version, must issue ACQUIRE memory barrier. */
1295- trx_id_t get_rw_trx_hash_version ()
1392+ trx_id_t get_rw_trx_hash_version () const noexcept
12961393 {
12971394 return m_rw_trx_hash_version.load (std::memory_order_acquire);
12981395 }
0 commit comments