@@ -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,130 @@ 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+ std::vector<trx_t *, ut_allocator<trx_t *>>
864+ trxs{ut_allocator<trx_t *>(mem_key_trx_sys_t_rw_trx_ids)};
865+ alignas (CPU_LEVEL1_DCACHE_LINESIZE ) mutable srw_spin_lock_low latch;
866+
867+ public:
868+ template <typename Functor1, typename Functor2>
869+ trx_id_t assign_new_trx_no (const trx_t *trx, Functor1 get_new_trx_id,
870+ Functor2 refresh_rw_trx_hash_version) noexcept
871+ {
872+ trx_id_t no;
873+ latch.rd_lock ();
874+ ut_ad (trx->rw_trx_ids_slot < ids.size ());
875+ ut_ad (trxs[trx->rw_trx_ids_slot ] == trx);
876+ ut_ad (ids[trx->rw_trx_ids_slot ].id == trx->id );
877+ ut_ad (ids[trx->rw_trx_ids_slot ].no == TRX_ID_MAX );
878+ ids[trx->rw_trx_ids_slot ].no = no= get_new_trx_id ();
879+ refresh_rw_trx_hash_version ();
880+ latch.rd_unlock ();
881+ return no;
882+ }
883+ trx_id_t snapshot_ids (trx_ids_t &view_ids,
884+ const trx_id_t max_trx_id) const noexcept
885+ {
886+ trx_id_t min_trx_no{max_trx_id};
887+ view_ids.clear ();
888+ latch.rd_lock ();
889+ view_ids.reserve (ids.size ());
890+ for (const auto &it : ids)
891+ {
892+ trx_id_t id{it.id };
893+ if (id < max_trx_id)
894+ {
895+ view_ids.push_back (id);
896+ const trx_id_t no{it.no };
897+ if (no < min_trx_no)
898+ min_trx_no= no;
899+ }
900+ }
901+ latch.rd_unlock ();
902+ return min_trx_no;
903+ }
904+ template <typename Functor1, typename Functor2>
905+ void register_rw (const trx_t *trx, Functor1 get_new_trx_id,
906+ Functor2 refresh_rw_trx_hash_version) noexcept
907+ {
908+ latch.rd_lock ();
909+ ut_ad (trx->rw_trx_ids_slot < ids.size ());
910+ ut_ad (trxs[trx->rw_trx_ids_slot ] == trx);
911+ ut_ad (ids[trx->rw_trx_ids_slot ].id == TRX_ID_MAX );
912+ ut_ad (ids[trx->rw_trx_ids_slot ].no == TRX_ID_MAX );
913+ ids[trx->rw_trx_ids_slot ].id = get_new_trx_id ();
914+ refresh_rw_trx_hash_version ();
915+ latch.rd_unlock ();
916+ }
917+ void deregister_rw (const trx_t *trx) noexcept
918+ {
919+ latch.rd_lock ();
920+ ut_ad (trx->rw_trx_ids_slot < ids.size ());
921+ rw_trx_id &slot= ids[trx->rw_trx_ids_slot ];
922+ ut_ad (trxs[trx->rw_trx_ids_slot ] == trx);
923+ ut_ad (slot.id == trx->id );
924+ slot.id = TRX_ID_MAX ;
925+ slot.no = TRX_ID_MAX ;
926+ latch.rd_unlock ();
927+ }
928+ void register_trx (trx_t *trx) noexcept
929+ {
930+ ut_ad (trx->rw_trx_ids_slot == std::numeric_limits<uint32_t >::max ());
931+ latch.wr_lock ();
932+ trx->rw_trx_ids_slot = static_cast <uint32_t >(ids.size ());
933+ ids.emplace_back ();
934+ trxs.emplace_back (trx);
935+ latch.wr_unlock ();
936+ }
937+ void deregister_trx (trx_t *trx) noexcept
938+ {
939+ latch.wr_lock ();
940+ ut_ad (trx->rw_trx_ids_slot < ids.size ());
941+ ut_ad (trxs[trx->rw_trx_ids_slot ] == trx);
942+ if (trx->rw_trx_ids_slot + 1 < ids.size ())
943+ {
944+ trx_t *move_trx= trxs.back ();
945+ ids[trx->rw_trx_ids_slot ]= std::move (ids.back ());
946+ trxs[trx->rw_trx_ids_slot ]= std::move (trxs.back ());
947+ move_trx->rw_trx_ids_slot = trx->rw_trx_ids_slot ;
948+ }
949+ ids.pop_back ();
950+ trxs.pop_back ();
951+ latch.wr_unlock ();
952+ ut_d (trx->rw_trx_ids_slot = std::numeric_limits<uint32_t >::max ());
953+ }
954+ void create () noexcept
955+ {
956+ ut_ad (ids.size () == 0 );
957+ latch.init ();
958+ }
959+ void destroy () noexcept
960+ {
961+ ut_ad (ids.size () == 0 );
962+ latch.destroy ();
963+ }
964+ };
965+
852966/* * The transaction system central memory data structure. */
853967class trx_sys_t
854968{
@@ -875,6 +989,15 @@ class trx_sys_t
875989
876990 /* * False if there is no undo log to purge or rollback */
877991 bool undo_log_nonempty;
992+
993+ /* *
994+ Collection of active read-write transaction identifiers and serialization
995+ numbers used for MVCC snapshot creation.
996+
997+ This complements rw_trx_hash with a traversal-friendly representation
998+ optimized for collecting active transaction ids.
999+ */
1000+ rw_trx_vector rw_trx_ids;
8781001public:
8791002 /* * List of all transactions. */
8801003 thread_safe_trx_ilist_t trx_list;
@@ -1014,7 +1137,7 @@ class trx_sys_t
10141137 next call to trx_sys.get_new_trx_id()
10151138 */
10161139
1017- trx_id_t get_max_trx_id ()
1140+ trx_id_t get_max_trx_id () const noexcept
10181141 {
10191142 return m_max_trx_id;
10201143 }
@@ -1037,7 +1160,7 @@ class trx_sys_t
10371160 Allocates and assigns new transaction serialisation number.
10381161
10391162 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
1163+ number becoming visible through rw_trx_ids . While we're in this gap
10411164 concurrent thread may come and do MVCC snapshot without seeing allocated
10421165 but not yet assigned serialisation number. Then at some point purge thread
10431166 may clone this view. As a result it won't see newly allocated serialisation
@@ -1047,58 +1170,43 @@ class trx_sys_t
10471170 m_rw_trx_hash_version is intended to solve this problem. MVCC snapshot has
10481171 to wait until m_max_trx_id == m_rw_trx_hash_version, which effectively
10491172 means that all transaction serialisation numbers up to m_max_trx_id are
1050- available through rw_trx_hash .
1173+ available through rw_trx_ids .
10511174
10521175 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 .
1176+ that m_rw_trx_hash_version increment happens after transaction serialisation
1177+ number becomes visible through rw_trx_ids .
10551178
10561179 @param trx transaction
10571180 */
1058- void assign_new_trx_no (trx_t *trx)
1181+ trx_id_t assign_new_trx_no (trx_t *trx)
10591182 {
1060- trx->rw_trx_hash_element ->no = get_new_trx_id_no_refresh ();
1061- refresh_rw_trx_hash_version ();
1183+ return rw_trx_ids.assign_new_trx_no (trx,
1184+ [this ](){ return get_new_trx_id_no_refresh (); },
1185+ [this ](){ refresh_rw_trx_hash_version (); });
10621186 }
10631187
10641188
10651189 /* *
10661190 Takes MVCC snapshot.
10671191
1068- To reduce malloc probablility we reserve rw_trx_hash.size() + 32 elements
1069- in ids.
1070-
10711192 For details about get_rw_trx_hash_version() != get_max_trx_id() spin
10721193 @sa register_rw() and @sa assign_new_trx_no().
10731194
10741195 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.
1076-
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.
1196+ that loading of m_rw_trx_hash_version happens before accessing rw_trx_ids.
10801197
1081- @param[in,out] caller_trx used to get access to rw_trx_hash_pins
10821198 @param[out] ids array to store registered transaction identifiers
10831199 @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
1200+
1201+ @return min(no)
10851202 */
10861203
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)
1204+ trx_id_t snapshot_ids (trx_ids_t &ids, trx_id_t &max_trx_id) const noexcept
10891205 {
1090- snapshot_ids_arg arg (ids);
1091-
1092- while ((arg.m_id = get_rw_trx_hash_version ()) != get_max_trx_id ())
1206+ while ((max_trx_id= get_rw_trx_hash_version ()) != get_max_trx_id ())
10931207 ut_delay (1 );
1094- arg.m_no = arg.m_id ;
10951208
1096- ids->clear ();
1097- ids->reserve (rw_trx_hash.size () + 32 );
1098- rw_trx_hash.iterate(caller_trx, copy_one_id, &arg);
1099-
1100- *max_trx_id= arg.m_id ;
1101- *min_trx_no= arg.m_no ;
1209+ return rw_trx_ids.snapshot_ids (ids, max_trx_id);
11021210 }
11031211
11041212
@@ -1149,7 +1257,7 @@ class trx_sys_t
11491257 Transaction becomes visible to MVCC.
11501258
11511259 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
1260+ visible through rw_trx_ids . While we're in this gap concurrent thread may
11531261 come and do MVCC snapshot. As a result concurrent read view will be able to
11541262 observe records owned by this transaction even before it was committed.
11551263
@@ -1165,21 +1273,32 @@ class trx_sys_t
11651273
11661274 void register_rw (trx_t *trx)
11671275 {
1168- trx->id = get_new_trx_id_no_refresh ();
1276+ rw_trx_ids.register_rw (trx,
1277+ [this , trx](){ return trx->id = get_new_trx_id_no_refresh (); },
1278+ [this ](){ refresh_rw_trx_hash_version (); });
11691279 rw_trx_hash.insert (trx);
1170- refresh_rw_trx_hash_version ();
1280+ }
1281+
1282+
1283+ void resurrect_rw (trx_t *trx)
1284+ {
1285+ rw_trx_ids.register_rw (trx, [trx](){ return trx->id ; }, [](){});
1286+ rw_trx_hash.insert (trx);
1287+ rw_trx_hash.put_pins (trx);
11711288 }
11721289
11731290
11741291 /* *
11751292 Deregisters read-write transaction.
11761293
1177- Transaction is removed from rw_trx_hash, which releases all implicit locks.
1178- MVCC snapshot won't see this transaction anymore.
1294+ After this call the transaction is no longer visible as active to MVCC read
1295+ views created subsequently, and all implicit locks held by the transaction
1296+ have been released.
11791297 */
11801298
1181- void deregister_rw (trx_t *trx)
1299+ void deregister_rw (trx_t *trx) noexcept
11821300 {
1301+ rw_trx_ids.deregister_rw (trx);
11831302 rw_trx_hash.erase (trx);
11841303 }
11851304
@@ -1204,6 +1323,7 @@ class trx_sys_t
12041323 void register_trx (trx_t *trx)
12051324 {
12061325 trx_list.push_front (*trx);
1326+ rw_trx_ids.register_trx (trx);
12071327 }
12081328
12091329
@@ -1214,6 +1334,7 @@ class trx_sys_t
12141334 */
12151335 void deregister_trx (trx_t *trx)
12161336 {
1337+ rw_trx_ids.deregister_trx (trx);
12171338 trx_list.remove (*trx);
12181339 }
12191340
@@ -1266,33 +1387,8 @@ class trx_sys_t
12661387private:
12671388 static my_bool find_same_or_older_callback (void *el, void *i) noexcept ;
12681389
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-
12941390 /* * Getter for m_rw_trx_hash_version, must issue ACQUIRE memory barrier. */
1295- trx_id_t get_rw_trx_hash_version ()
1391+ trx_id_t get_rw_trx_hash_version () const noexcept
12961392 {
12971393 return m_rw_trx_hash_version.load (std::memory_order_acquire);
12981394 }
0 commit comments