别的网站可以做弹幕自己做seo网站推广

bicheng/2026/1/17 22:23:38/文章来源:
别的网站可以做弹幕,自己做seo网站推广,建一个网站怎么赚钱吗,个人空间网站建设报告基础概念 并发事务带来的问题 1#xff09;脏读#xff1a;一个事务读取到另一个事务更新但还未提交的数据#xff0c;如果另一个事务出现回滚或者进一步更新#xff0c;则会出现问题。 2#xff09;不可重复读#xff1a;在一个事务中两次次读取同一个数据时#xff0c…基础概念 并发事务带来的问题 1脏读一个事务读取到另一个事务更新但还未提交的数据如果另一个事务出现回滚或者进一步更新则会出现问题。 2不可重复读在一个事务中两次次读取同一个数据时由于在两次读取之间另一个事务修改了该数据所以出现两次读取的结果不一致。 3幻读在一个事务中使用相同的 SQL 两次读取第二次读取到了其他事务新插入的行。 要解决这些并发事务带来的问题一个比较简单粗暴的方法是加锁但是加锁必然会带来性能的降低因此 MySQL 使用了 MVCC 来提升并发事务下的性能。 MVCC 带来的好处 如果没有 MVCC为了保证并发事务的安全一个比较容易想到的办法就是加读写锁实现读读不冲突、读写冲突、写读冲突写写冲突在这种情况下并发读写的性能必然会收到严重影响。 而通过 MVCC我们可以做到读写之间不冲突我们读的时候只需要将当前记录拷贝一份到内存中ReadView之后该事务的查询就只跟 ReadView 打交道不影响其他事务对该记录的写操作。 事务隔离级别 1读未提交Read Uncommitted最低的隔离级别会读取到其他事务还未提交的内容存在脏读。 2读已提交Read Committed读取到的内容都是已经提交的可以解决脏读但是存在不可重复读。 3可重复读Repeatable Read在一个事务中多次读取时看到相同的内容可以解决不可重复读但是存在幻读。在 InnoDB 中不存在幻读问题对于快照读InnoDB 使用 MVCC 解决幻读对于当前读InnoDB 通过 gap locks 或 next-key locks 解决幻读。 4串行化Serializable最高的隔离级别串行的执行事务没有并发事务问题。 InnoDB MVCC 实现 核心数据结构 trx_sys_t事务系统中央存储器数据结构 struct trx_sys_t {TrxSysMutex mutex; /*! 互斥锁 */ ​MVCC *mvcc; /*! mvcc */ ​volatile trx_id_t max_trx_id; /*! 要分配给下一个事务的事务id*/ ​std::atomictrx_id_t min_active_id; /*! 最小的活跃事务Id */// 省略... ​trx_id_t rw_max_trx_id; /*! 最大读写事务Id */ ​// 省略... ​trx_ids_t rw_trx_ids; /*! 当前活跃的读写事务Id列表 */ ​Rsegs rsegs; /*! 回滚段 */ ​// 省略... }; MVCCMVCC 读取视图管理器 class MVCC {public:// 省略... ​/** 创建一个视图 */void view_open(ReadView *view, trx_t *trx); ​/** 关闭一个视图 */void view_close(ReadView *view, bool own_mutex); ​/** 释放一个视图 */void view_release(ReadView *view); ​// 省略... ​/** 判断视图是否处于活动和有效状态 */static bool is_view_active(ReadView *view) {ut_a(view ! reinterpret_castReadView *(0x1)); ​return (view ! NULL !(intptr_t(view) 0x1));} ​// 省略... ​private:typedef UT_LIST_BASE_NODE_T(ReadView) view_list_t; ​/** 空闲可以被重用的视图*/view_list_t m_free; ​/** 活跃或者已经关闭的 Read View 的链表 */view_list_t m_views; }; ReadView视图某一时刻的一个事务快照 class ReadView { ​// 省略... ​private:/** 高水位大于等于这个ID的事务均不可见 */trx_id_t m_low_limit_id; ​/** 低水位小于这个ID的事务均可见 */trx_id_t m_up_limit_id; ​/** 创建该 Read View 的事务ID */trx_id_t m_creator_trx_id; ​/** 创建视图时的活跃事务id列表*/ids_t m_ids; ​/** 配合purge标识该视图不需要小于 m_low_limit_no 的 UNDO LOG如果其他视图也不需要则可以删除小于 m_low_limit_no 的 UNDO LOG */trx_id_t m_low_limit_no; ​/** 标记视图是否被关闭*/bool m_closed; ​// 省略... }; 基本理论基础 当前读和快照读 当前读官方叫做 Locking Reads锁定读取读取数据的最新版本。常见的 update/insert/delete、还有 select ... for update、select ... lock in share mode 都是当前读。 官方文档MySQL :: MySQL 8.0 Reference Manual :: 15.7.2.4 Locking Reads 快照读官方叫做 Consistent Nonlocking Reads一致性非锁定读取也叫一致性读取读取快照版本也就是 MVCC 生成的 ReadView。用于普通的 select 的语句。MySQL 默认快照读。 官方文档MySQL :: MySQL 8.0 Reference Manual :: 15.7.2.3 Consistent Nonlocking Reads 低水位线与高水位线 m_up_limit_id低水位线活跃事务的最小 IDm_low_limit_id高水位线活跃事务的最大 ID 1 当一个事务修改一条记录时会将该事务 ID 插入到当前记录并存储与 DB_TRX_ID 字段中。 活跃事务是指在创建 ReadView 时启动但未提交的事务MySQL 源码中使用 m_idsm_ids 中的元素递增存储的结构保存活跃事务 ID。 事务可见性与高低水位线的关系 1已提交事务对当前事务一定可见 2未开始事务对当前事务一定不可见 3当事务 ID 介于 m_up_limit_id 和 m_low_limit_id 之间时需判断事务 ID 是否位于 m_ids 中 如果事务 ID 位于 m_ids 中则对当前事务不可见因为事务还未提交否则对当前事务可见 增加隐藏字段 为了实现 MVCCInnoDB 会向表中添加三个隐藏字段DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR。 DB_ROW_ID该字段占 6 个字节在建表时如果没有显示指定主键InnoDB 会首先判断该表中是否有非空唯一索引如果有则该索引即为主键并将主键值存放到该字段中如果存在多个非空唯一索引则选择第一个创建的非空唯一索引为主键如果没有非空唯一索引InnoDB 会隐式生成主键并存储于当前字段中DB_TRX_ID该字段占 6 个字节用于存储最近修改或插入当前记录的事务IDDB_ROLL_PTR该字段占 7 个字节指向写入回滚段的 undo log 记录。每次对某条记录进行更新时会通过 undo log 记录更新前的行记录内容更新后的行记录会通过 DB_ROLL_PTR 指向该 undo log 。当某条记录被多次修改时该行记录会存在多个版本通过 DB_ROLL_PTR 链接形成一个类似版本链的概念大致如下图所示。 说明隐藏列 DB_ROW_ID 是早期 MySQL 版本的叫法在后续版本中为了与应用程序更好地兼容这个列的名称被更改为 _rowid。 隐藏字段源码解析 上述三个隐藏字段在 MySQL 源码 .\mysql\storage\innobase\dict\dict0dict.cc 文件的 dict_table_add_system_columns 函数中具体函数内容如下 /** Adds system columns to a table object. // 添加系统列到表对象 param[in,out] table Table param[in] heap Temporary heap */ void dict_table_add_system_columns(dict_table_t *table, mem_heap_t *heap) {ut_ad(table);ut_ad(table-n_def (table-n_cols - table-get_n_sys_cols()));ut_ad(table-magic_n DICT_TABLE_MAGIC_N);ut_ad(!table-cached);/* NOTE: the system columns MUST be added in the following order(so that they can be indexed by the numerical value of DATA_ROW_ID,etc.) and as the last columns of the table memory object.The clustered index will not always physically contain all systemcolumns.Intrinsic table dont need DB_ROLL_PTR as UNDO logging is turned offfor these tables. */// 添加主键 IDdict_mem_table_add_col(table, heap, DB_ROW_ID, DATA_SYS,DATA_ROW_ID | DATA_NOT_NULL, DATA_ROW_ID_LEN, false);// 添加事务 IDdict_mem_table_add_col(table, heap, DB_TRX_ID, DATA_SYS,DATA_TRX_ID | DATA_NOT_NULL, DATA_TRX_ID_LEN, false);if (!table-is_intrinsic()) { // 判断是否是内部表// 添加回滚指针dict_mem_table_add_col(table, heap, DB_ROLL_PTR, DATA_SYS,DATA_ROLL_PTR | DATA_NOT_NULL, DATA_ROLL_PTR_LEN,false);/* This check reminds that if a new system column is added tothe program, it should be dealt with here */} } 增删改的底层操作 更新操作 当我们更新一条数据时InnoDB 会进行如下操作 加锁对要更新的行记录加排他锁写 undo log将更新前的记录写入 undo log并构建指向该 undo log 的回滚指针 roll_ptr更新行记录更新行记录的 DB_TRX_ID 属性为当前的事务 ID更新 DB_ROLL_PTR 属性为步骤 2 生成的回滚指针将此次要更新的属性列更新为目标值写 redo logDB_ROLL_PTR 使用步骤 2 生成的回滚指针DB_TRX_ID 使用当前的事务 ID并填充更新后的属性值处理结束释放排他锁 删除操作 删除操作在底层实现中使用更新来实现逻辑基本和更新操作一样如下是几个需要注意的点 1写 undo log 期间会通过 type_cmpl 来标识是删除操作还是更新操作且不记录列的旧值 2这边不会直接删除只会给行记录的 info_bits 打上删除标识REC_INFO_DELETED_FLAG之后会由专门的 purge 线程来执行真正的删除操作 插入操作 插入操作相比于更新操作更为简单就是新增一条记录DB_TRX_ID 使用当前的事务Id同样会有 undo log 和 redo log。 更新记录源码解析 更新行记录在 MySQL 源码 .\mysql\storage\innobase\btr\btr0cur.cc 文件的 btr_cur_update_in_place 函数中具体函数内容如下 /** Updates a record when the update causes no size changes in its fields. */ dberr_t btr_cur_update_in_place(ulint flags, btr_cur_t *cursor, ulint *offsets,const upd_t *update, ulint cmpl_info,que_thr_t *thr, trx_id_t trx_id, mtr_t *mtr) {// 省略...// 通过 B 树游标获取当前记录rec btr_cur_get_rec(cursor);index cursor-index; // 或者当前游标所指的索引// 省略...// 写 undo logerr btr_cur_upd_lock_and_undo(flags, cursor, offsets, update, cmpl_info,thr, mtr, roll_ptr);// 省略...if (!(flags BTR_KEEP_SYS_FLAG) !index-table-is_intrinsic()) {// 更新 rec 的 trx_id、roll_ptr 属性值row_upd_rec_sys_fields(rec, nullptr, index, offsets, thr_get_trx(thr),roll_ptr);}// 获取记录的删除标记如果记录被标记为删除则返回 truewas_delete_marked rec_get_deleted_flag(rec, page_is_comp(buf_block_get_frame(block)));// 省略...// 将 rec 要更新的属性列更新为目标值row_upd_rec_in_place(rec, index, offsets, update, page_zip);// 写 redo logbtr_cur_update_in_place_log(flags, rec, index, update, trx_id, roll_ptr, mtr);// 如果当前记录被标记为删除且还没有被删除则取消删除标记因为字段的所有权已经转移到了新记录上if (was_delete_marked !rec_get_deleted_flag(rec, page_is_comp(buf_block_get_frame(block)))) {/* The new updated record owns its possible externallystored fields */lob::BtrContext btr_ctx(mtr, nullptr, index, rec, offsets, block);btr_ctx.unmark_extern_fields();}// 省略...return (err); } 构建一致性读取视图ReadView 当我们的隔离级别为 RR 时每开启一个事务系统会给该事务会分配一个事务 Id在该事务执行第一个 select 语句的时候会生成一个当前时间点的事务快照 ReadView核心属性如下 m_ids创建 ReadView 时当前系统中活跃的事务 Id 列表可以理解为生成 ReadView 那一刻还未执行提交的事务并且该列表是个升序列表。m_up_limit_id低水位小于低水位的事务都可见取 m_ids 列表的第一个节点因为 m_ids 是升序列表因此也就是 m_ids 中事务 Id 最小的那个。m_low_limit_id高水位大于等于高水位的事务都不可见生成 ReadView 时系统将要分配给下一个事务的 Id 值即 m_ids 列表的最后一个节点加 1。m_creator_trx_id创建 ReadView 的事务 Id。 一致性读取视图源码解析 一致性读取视图在 MySQL 源码 .\mysql\storage\innobase\read\read0read.cc 文件的 ReadView::prepare 函数中具体函数内容如下 调用堆栈row_search_mvcc trx_assign_read_view MVCC::view_open ReadView::prepare /** Opens a read view where exactly the transactions serialized before this point in time are seen in the view. param id Creator transaction id */void ReadView::prepare(trx_id_t id) {ut_ad(trx_sys_mutex_own());m_creator_trx_id id; // 创建 ReadView 的事务 IDm_low_limit_no trx_get_serialisation_min_trx_no(); // 视图不需要查看小于该值的事务 ID// 高水位m_low_limit_id trx_sys_get_next_trx_id_or_no();ut_a(m_low_limit_no m_low_limit_id);// 如果事务系统的活跃事务列表不为空则将其拷贝至 m_idsif (!trx_sys-rw_trx_ids.empty()) {/*1) copy_trx_ids 函数通过调用 m_up_limit_id m_ids.front() 将 m_ids 中的最小事务 id 赋值给 m_up_limit_id;2) 使用二分查找算法查找 trx_id 是否在 m_ids 中;*/ copy_trx_ids(trx_sys-rw_trx_ids);} else { // 否则清空 ReadView 的活跃事务列表m_ids.clear();}/* The first active transaction has the smallest id. */m_up_limit_id !m_ids.empty() ? m_ids.front() : m_low_limit_id;ut_a(m_up_limit_id m_low_limit_id);ut_d(m_view_low_limit_no m_low_limit_no);m_closed false; } 最后会将这个创建的 ReadView 添加到 MVCC 的 m_views 中。 一致性视图可见性判断 SQL 查询走聚簇索引 有了这个 ReadView在访问某条记录时只需要按照如下步骤即可判断当前记录的某个版本是否可见 1如果被访问记录的 trx_id 与 ReadView 中 的 m_creator_trx_id 值相同则意味着当前事务在访问它自己修改过的记录所以该记录可以被当前事务访问。 2如果被访问记录的 trx_id 小于 ReadView 中的 m_up_limit_id低水位表明该版本记录的事务在当前事务生成 ReadView 前已经提交所以该版本记录可以被当前事务访问。 3如果被访问记录的 trx_id 大于等于 ReadView 中的 m_low_limit_id高水位表明该版本记录的事务在当前事务生成 ReadView 后才开启所以该版本不可以被当前事务访问。 4如果被访问记录的 trx_id 属性值在 ReadView 的 m_up_limit_id 和 m_low_limit_id 之间那就需要判断 trx_id 属性值是不是在 m_ids 列表中源码中会通过二分法查找。如果在说明创建 ReadView 时生成该版本记录的事务还是活跃的该版本记录不可以被访问如果不在说明创建 ReadView 时生成该版本记录的事务已经被提交该版本可以被访问。 在进行判断时首先会拿记录的最新版本来比较如果该版本记录无法被当前事务看到则通过记录的 DB_ROLL_PTR 找到上一个版本重新进行比较直到找到一个能被当前事务看到的版本。 而对于删除其实就是一种特殊的更新InnoDB 在 info_bits 中用一个标记位 delete_flag 标识是否删除。当我们在进行判断时会检查下 delete_flag 是否被标记如果是则会根据情况进行处理 如果索引是聚簇索引并且具有唯一特性主键、唯一索引等则返回 DB_RECORD_NOT_FOUND否则会寻找下一条记录继续流程 其实很容易理解如果是唯一索引查询必然只有一条记录如果被删除了则直接返回空而如果是普通索引可能存在多个相同值的行记录该行不存在则继续查找下一条。 注意以上内容是对于 RR可重复读级别来说而对于 RC读提交级别其实整个过程几乎一样唯一不同的是生成 ReadView 的时机RR 级别只在事务第一次 select 时生成一次之后一直使用该 ReadView。而 RC 级别则在每次 select 时都会生成一个 ReadView。 聚簇索引可见性源码解析 走聚簇索引的核心流程在 row_search_mvcc 函数中具体调用堆栈row_search_mvcc lock_clust_rec_cons_read_sees changes_visible。 1row_search_mvcc 函数 // 功能使用游标在数据库中搜索行 dberr_t row_search_mvcc(byte *buf, page_cur_mode_t mode,row_prebuilt_t *prebuilt, ulint match_mode,const ulint direction) {// 省略.../*-------------------------------------------------------------*/// 阶段1:尝试从记录缓冲区或预取缓存中弹出行// 省略.../*-------------------------------------------------------------*/// 阶段2:如果可能的话尝试快速自适应散列索引搜索// 省略.../*-------------------------------------------------------------*/// 阶段3:打开或恢复索引游标位置// 省略.../*-------------------------------------------------------------*//* 阶段4:在一个循环中查找匹配的记录 */// 省略...// 检查隔离级别是否是读未提交如果是什么也不做if (trx-isolation_level TRX_ISO_READ_UNCOMMITTED) { /* Do nothing: we let a non-locking SELECT read thelatest version of the record */} else if (index clust_index) { /*------ 聚簇索引 ------*/ /* --------------------------------------------------- */// 判断 rec 在 ReadView 中是否可见if (srv_force_recovery 5 !lock_clust_rec_cons_read_sees(rec, index, offsets,trx_get_read_view(trx))) { rec_t *old_vers;// 获取 rec 的上一个版本并将其赋值给 old_verserr 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());if (err ! DB_SUCCESS) {goto lock_wait_or_error;}// 如果旧版本中的行记录也为空或不可见则继续处理下一条记录if (old_vers nullptr) {goto next_rec;}rec old_vers; // 记录指针更新为先前版本的记录prev_rec rec;}} else { /*------ 非聚簇索引 ------*/ /* --------------------------------------------------- */// 判断 rec 在 ReadView 中的可见性if (!srv_read_only_mode !lock_sec_rec_cons_read_sees(rec, index, trx-read_view)) {/* 注lock_sec_rec_cons_read_sees 函数判断可见性逻辑page页最大事务ID小于低水位即可见page页最大事务ID大于等于低水位时认为是不可见但在goto requires_clust_rec时会进一步判断当前记录的可见性*/ switch (row_search_idx_cond_check(buf, prebuilt, rec, offsets)) {case ICP_NO_MATCH: // 不满足二级索引条件当有多条记录时排除其它记录icp下推二级索引过滤goto next_rec;case ICP_OUT_OF_RANGE: // 超出二级索引范围err DB_RECORD_NOT_FOUND;goto idx_cond_failed;case ICP_MATCH: // 满足二级索引条件通过回表查找当前记录的事务id以获取当前事务的上一版本数据goto requires_clust_rec;}ut_error; // 出现了意外情况抛出错误}}}/*-------------------------------------------------------------*//* 阶段5:将光标移动到下一个索引记录 */// 省略... } 2lock_clust_rec_cons_read_sees 函数 /** Checks that a record is seen in a consistent read.return true if sees, or false if an earlier version of the recordshould be retrieved */ bool lock_clust_rec_cons_read_sees(const rec_t *rec, /*! in: user record which should be read orpassed over by a read cursor */ dict_index_t *index, /*! in: clustered index */const ulint *offsets, /*! in: rec_get_offsets(rec, index) */ReadView *view) /*! in: consistent read view */ {ut_ad(index-is_clustered()); ut_ad(page_rec_is_user_rec(rec)); ut_ad(rec_offs_validate(rec, index, offsets)); /* Temp-tables are not shared across connections and multipletransactions from different connections cannot simultaneouslyoperate on same temp-table and so read of temp-table isalways consistent read. *//* 如果数据库处于只读模式或者表是临时的则返回true表示始终可以看到该记录。这是因为临时表在多个连接和事务中不是共享的所以读取总是具有一致性。*/if (srv_read_only_mode || index-table-is_temporary()) {ut_ad(view nullptr || index-table-is_temporary());return (true);}/* NOTE that we call this function while holding the searchsystem latch. */// 获取当前记录的事务ID// 主键索引扫描时rec是一条完整记录可以直接获取当前记录的事务id trx_id_t trx_id row_get_rec_trx_id(rec, index, offsets);// 判断 ReadView 对当前记录的可见性return (view-changes_visible(trx_id, index-table-name)); } 3changes_visible 函数 /** Check whether the changes by id are visible. param[in] id transaction id to check against the view param[in] name table name return whether the view sees the modifications of id. */ [[nodiscard]] bool changes_visible(trx_id_t id,const table_name_t name) const {ut_ad(id 0); // 事务id一般是正整数0 可能表示“没有事务”或“无效事务”/* m_up_limit_id 为低水位线小于低水位线的事务ID都可见m_creator_trx_id 为当前事务id记录被当前事务修改时也一定可见*/if (id m_up_limit_id || id m_creator_trx_id) {return (true);}// 检查事务ID是否有效check_trx_id_sanity(id, name);// m_low_limit_id 为高水位线所有大于高水位线的事务ID都不可见if (id m_low_limit_id) {return (false);// 判断创建 ReadView 时的活跃事务列表是否为空若为空则当前记录可见} else if (m_ids.empty()) { return (true);}const ids_t::value_type *p m_ids.data();// 使用二分查找在 m_ids 中查找当前记录的事务ID若找到则当前记录不可见若未找到则可见return (!std::binary_search(p, p m_ids.size(), id)); } 获取当前记录的上一个版本源码解析 获取当前记录的上一个版本主要是通过 DB_ROLL_PTR 来实现核心流程如下 1获取当前记录的回滚指针 DB_ROLL_PTR、获取当前记录的事务 ID 2通过回滚指针拿到对应的 undo log 3解析 undo log并使用 undo log 构建用于更新向量 UPDATE 4构建记录的上一个版本先用记录的当前版本填充然后使用 UPDATEundo log进行覆盖。 获取当前记录的上一个版本的核心流程在 row_search_mvcc 函数中具体调用堆栈row_search_mvcc row_sel_build_prev_vers_for_mysql row_vers_build_for_consistent_read  trx_undo_prev_version_build。 bool trx_undo_prev_version_build(const rec_t *index_rec ATTRIB_USED_ONLY_IN_DEBUG,mtr_t *index_mtr ATTRIB_USED_ONLY_IN_DEBUG, const rec_t *rec,const dict_index_t *const index, ulint *offsets, mem_heap_t *heap,rec_t **old_vers, mem_heap_t *v_heap, const dtuple_t **vrow, ulint v_status,lob::undo_vers_t *lob_undo) {// 省略...// 1) 获取 rec 的回滚指针 roll_ptrroll_ptr row_get_rec_roll_ptr(rec, index, offsets);*old_vers nullptr;if (trx_undo_roll_ptr_is_insert(roll_ptr)) {/* The record rec is the first inserted version */return true;}// 2) 获取 rec 的事务 IDrec_trx_id row_get_rec_trx_id(rec, index, offsets);/* REDO rollback segments are used only for non-temporary objects.For temporary objects NON-REDO rollback segments are used. */bool is_temp index-table-is_temporary();ut_ad(!index-table-skip_alter_undo);// 3) 通过回滚指针获取 undo log 记录拷贝到 heap 中并且填充给 undo_recif (trx_undo_get_undo_rec(roll_ptr, rec_trx_id, heap, is_temp,index-table-name, undo_rec)) {// 省略...}// 4) 开始解析 undo log填充给对应的参数参考 undo log 的构造图这边会按构造顺序依次读取填充type_cmpl_t type_cmpl;// 4.1) 通过 undo_rec 读取 type、undo_no、cmpl_info、table_id 的值并填充到形参变量中// 读取这些值后将 undo log 的剩余部分返回并赋值给 ptrptr trx_undo_rec_get_pars(undo_rec, type, cmpl_info, dummy_extern,undo_no, table_id, type_cmpl);// 省略...// 4.2) 从 ptr 读取 trx_id、roll_ptr、info_bits 的值并填充到形参参的变量中// 读取这些值后将 undo log 的剩余部分返回并赋值给 ptrptr trx_undo_update_rec_get_sys_cols(ptr, trx_id, roll_ptr, info_bits);// 4.3) undo log 跳过行引用ptr trx_undo_rec_skip_row_ref(ptr, index);// 4.4) 通过 undo log 的剩余部分(roll_ptr、info_bts等)构建 update(就是填充到update变量中)ptr trx_undo_update_rec_get_update(ptr, index, type, trx_id, roll_ptr,info_bits, nullptr, heap, update,lob_undo, type_cmpl);ut_a(ptr);if (row_upd_changes_field_size_or_external(index, offsets, update)) {// 省略...} else {buf static_castbyte *(mem_heap_alloc(heap, rec_offs_size(offsets)));// 5) 构建 old_vers先用 rec 填充再用 update 覆盖(也就是 rec 的 roll_ptr 指向的 undo 1og 的内容)// 5.1) 将 rec 拷贝到 buf 中同时返回指向 buf 数据部分的指针赋值给 old_vers*old_vers rec_copy(buf, rec, offsets);rec_offs_make_valid(*old_vers, index, offsets);// 5.2) 使用 update 覆盖 old_vers相当于先将 old_vers 赋值为 rec(当前版本)然后用 undo log 来覆盖row_upd_rec_in_place(*old_vers, index, offsets, update, nullptr);}// 省略... } SQL 查询走非聚簇索引 当走普通索引时判断逻辑如下 1判断被访问索引记录所在页的最大事务 Id 是否小于 ReadView 中的 m_up_limit_id低水位如果是则代表该页的最后一次修改事务 ID 在 ReadView 创建前之前已经提交所以一定可以访问如果不是并不代表一定不可以访问道理跟走聚簇索引一样事务 ID 大的也可能提交比较早所以需要做进一步判断见步骤 2。 2根据索引信息使用 ICPIndex Condition Pushdown来判断搜索条件是否满足主要目的是在使用聚簇索引判断记录可见性之前先进行过滤可以减少不必要的回表操作这边有三种情况 ICP 判断不满足条件但没有超出扫描范围则获取下一条记录继续查找如果不满足条件并且超出扫描返回则返回 DB_RECORD_NOT_FOUND如果 ICP 判断符合条件则会获取对应的聚簇索引来进行可见性判断。 说明在进行非聚簇索引可见性判断时若不可见但满足 ICP 条件则会继续调用 lock_clust_rec_cons_read_sees 函数再次判断当前记录的可见性。 非聚簇索引可见性判断源码解析 走非聚簇索引的核心流程在 row_search_mvcc 函数中具体调用堆栈row_search_mvcc lock_sec_rec_cons_read_seese。 1row_search_mvcc 函数 // 功能使用游标在数据库中搜索行 dberr_t row_search_mvcc(byte *buf, page_cur_mode_t mode,row_prebuilt_t *prebuilt, ulint match_mode,const ulint direction) {// 省略.../*-------------------------------------------------------------*/// 阶段1:尝试从记录缓冲区或预取缓存中弹出行// 省略.../*-------------------------------------------------------------*/// 阶段2:如果可能的话尝试快速自适应散列索引搜索// 省略.../*-------------------------------------------------------------*/// 阶段3:打开或恢复索引游标位置// 省略.../*-------------------------------------------------------------*//* 阶段4:在一个循环中查找匹配的记录 */// 省略...// 检查隔离级别是否是读未提交如果是什么也不做if (trx-isolation_level TRX_ISO_READ_UNCOMMITTED) { /* Do nothing: we let a non-locking SELECT read thelatest version of the record */} else if (index clust_index) { /*------ 聚簇索引 ------*/ /* --------------------------------------------------- */// 判断 rec 在 ReadView 中是否可见if (srv_force_recovery 5 !lock_clust_rec_cons_read_sees(rec, index, offsets,trx_get_read_view(trx))) { rec_t *old_vers;// 获取 rec 的上一个版本并将其赋值给 old_verserr 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());if (err ! DB_SUCCESS) {goto lock_wait_or_error;}// 如果旧版本中的行记录也为空或不可见则继续处理下一条记录if (old_vers nullptr) {goto next_rec;}rec old_vers; // 记录指针更新为先前版本的记录prev_rec rec;}} else { /*------ 非聚簇索引 ------*/ /* --------------------------------------------------- */// 判断 rec 在 ReadView 中的可见性if (!srv_read_only_mode !lock_sec_rec_cons_read_sees(rec, index, trx-read_view)) {/* 注lock_sec_rec_cons_read_sees 函数判断可见性逻辑page页最大事务ID小于低水位即可见page页最大事务ID大于等于低水位时认为是不可见但在goto requires_clust_rec时会进一步判断当前记录的可见性*/ switch (row_search_idx_cond_check(buf, prebuilt, rec, offsets)) {case ICP_NO_MATCH: // 不满足二级索引条件当有多条记录时排除其它记录icp下推二级索引过滤goto next_rec;case ICP_OUT_OF_RANGE: // 超出二级索引范围err DB_RECORD_NOT_FOUND;goto idx_cond_failed;case ICP_MATCH: // 满足二级索引条件通过回表查找当前记录的事务id以获取当前事务的上一版本数据goto requires_clust_rec;}ut_error; }}}// 省略...// 如果不是在主键上扫描则判断是否需要回表if (index ! clust_index prebuilt-need_to_access_clustered) { // MRR 的情况下 need_to_access_clustered falserequires_clust_rec: // 主要用于非聚簇索引情况下的回表// 省略...// 使用二级索引对应的主键进行回表拿到完整记录后会在该函数中调用lock_clust_rec_cons_read_sees函数再次判断当前记录的可见性err row_sel_get_clust_rec_for_mysql(prebuilt, index, rec, thr, clust_rec, offsets, heap,need_vrow ? vrow : nullptr, mtr, prebuilt-get_lob_undo());// 省略.../*-------------------------------------------------------------*//* 阶段5:将光标移动到下一个索引记录 */// 省略... } 2lock_sec_rec_cons_read_sees 函数 /** Checks that a non-clustered index record is seen in a consistent read. */ bool lock_sec_rec_cons_read_sees(const rec_t *rec, /*! in: user record whichshould be read or passed overby a read cursor */const dict_index_t *index, /*! in: index */const ReadView *view) /*! in: consistent read view */ {ut_ad(page_rec_is_user_rec(rec)); /* NOTE that we might call this function while holding the searchsystem latch. */// 当crash时使用redo log恢复记录期间为falseif (recv_recovery_is_on()) {return (false);// 临时表的读取始终是一致的因为临时表在多个连接和事务中不是共享的所以读取总是具有一致性} else if (index-table-is_temporary()) { return (true);}// 从记录所在页获取最大事务ID是一种优化以减少回表// 二级索引扫描 rec 中存储的只是主键 id如果不使用 page 的最大事务 ID可能需要进行大量回表以获取当前记录的事务 IDtrx_id_t max_trx_id page_get_max_trx_id(page_align(rec)); ut_ad(max_trx_id 0); // 确保最大的事务ID大于0// sees 函数通过判断 max_trx_id 是否小于低水位线(m_up_limit_id)若小于则可见return (view-sees(max_trx_id)); } MVCC 是否解决了幻读 MVCC 解决了部分幻读但并没有完全解决幻读。 对于快照读MVCC 因为从 ReadView 读取所以必然不会看到新插入的行解决了幻读问题。 对于当前读MVCC 无法解决幻读。需要使用 Gap Lock 或 Next-Key LockGap Lock Record Lock来解决。 其实原理也很简单用上面的例子稍微修改下以触发当前读select * from user where id 10 for update当使用了 Gap Lock 时Gap 锁会锁住 id 10 的整个范围因此其他事务无法插入 id 10 的数据从而防止了幻读。 Repeatable Read 如何解决幻读 SQL 标准中规定的 RR 并不能消除幻读但是 MySQL InnoDB 的 RR 可以靠的就是 Gap 锁。在 RR 级别下Gap 锁是默认开启的而在 RC 级别下Gap 锁是关闭的。 RR 与 RC 生成 ReadView 的时机与区别 假设有一张表其结构和数据如下 mysql show create table t; ------------------------------------------------------------------------- | Table | Create Table | ------------------------------------------------------------------------- | t | CREATE TABLE t (id int NOT NULL,a int DEFAULT NULL,PRIMARY KEY (id),KEY a (a) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_general_ci | ------------------------------------------------------------------------ 1 row in set (0.00 sec)mysql select * from t; ---------- | id | a | ---------- | 1 | 50 | ---------- 1 row in set (0.00 sec)RR 或 RC 生成 ReadView 的时机 解析RR 生成 ReadView 的时机是事务第一个 select 的时候而不是事务开始的时候。右边的例子中事务1在事务2提交了修改后才执行第一个 select因此生成的 ReadView 中a 的是 100 而不是事务1刚开始时的 50。 RR 与 RC 生成 ReadView 的区别 解析RR 只在事务第一次执行 select 时生成 ReadView 之后一直使用该 ReadView。而 RC 级别则在每次执行 select 时都会生成一个 ReadView所以在第二次执行 select 时读取到了事务 2 对于 a 的修改值。 如果需要本文 WORD、PDF 相关文档请在评论区留言  如果需要本文 WORD、PDF 相关文档请在评论区留言  如果需要本文 WORD、PDF 相关文档请在评论区留言  参考 MVCC的底层原理_mysql mvcc 高水位低水位-CSDN博客 MVCC原理分析 源码解读 -- 必须说透_mvcc mysql源码解析-CSDN博客 MySQL 8.0 MVCC 核心原理解析核心源码 - 知乎 (zhihu.com) InnoDB MVCC 详解-腾讯云开发者社区-腾讯云 (tencent.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/89413.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

奈曼旗建设局网站魔方 网站建设 有限公司

前言 前面主要讲述的是方程组和矩阵的关系,现在了解下矩阵和矩阵的关系 方阵的特征值与特征向量 假设A为n阶方阵,对于一个数 λ \lambda λ 若存在:非零列向量 α \alpha α,使得: A α ⃗ λ α ⃗ A\vec{\alp…

论坛网站平台建设方案wordpress刷赞网站源码

概览 B树(作为B树访问方法实现)是一种数据结构,它使您能够通过从树的根向下查找树的叶节点中所需的元素。为了明确地标识搜索路径,必须对所有树元素进行排序。B树是为有序数据类型设计的,这些数据类型的值可以进行比较和排序。 下面的机场代…

网站建设工程师做论坛网站如何赚钱

STM32 的某些系列 MCU 自带 EEPROM。笔者使用的 STM32L151RET6 自带 16 KB 的 EEPROM,可以用来存储自定义的数据。在芯片选型时,自带 EEPROM 也可以作为一个考量点,省去了在外接 EEPROM 的烦恼。 下面简单介绍下 STM32 内部 EEPROM 的读写流…

青羊区建设局网站开网店如何运营和推广

1.导入图片 2.用魔法棒点击图片 3.点选择,反选 4.选择,选择并遮住 5.用画笔修饰证件照边缘 6. 7.更换要换的底的颜色 8.新建图层 9.使用快捷键altdelete键填充颜色。 10.移动图层,完成换底。

朝阳做网站网上电子商城系统

教育 -计算机网络-章节资料考试资料-四川农业大学【】 随堂测验 1、【单选题】以下哪一项不属于物联网的实现基础 A、可穿戴设备 B、RFID C、APP D、蓝牙 参考资料【 】 2、【单选题】以下哪一项不是解决网络安全问题的因素 A、 安全技术 B、法律法规 C、道德自律 D、多种应用 …

介绍化工项目建设和招聘的网站做的比较早的海淘网站

目录 🐶3.2.1 分区过程 🐶3.2.2 SplitSize计算和分区个数计算 🐶3.2.3 Partition的数目设置 1. 🥙对于数据读入阶段,输入文件被划分为多少个InputSplit就会需要多少初始task. 2. 🥙对于转换算子产生的…

电子商城网站开发价格北京做网站的网络公司

Windows phone的页面布局方式一般是依赖布局控件实现的,而布局控件有三种Grid,StackPanel和Canvas Grid是网格布局方式,相当于一个表格,有行和列,新建一个Windows phone项目,打开MainPage.xaml,…

弄网站赚钱吗wordpress源码系统下载地址

cesium不同版本对3dtiles的渲染效果不同,固定光照的优化方案,避免map.fixedLight true,导致的光照效果太强,模型太亮的问题。 问题来源: 1.Cesium1.47版本加载tileset.json文件跟Mars3d最新版加载文件存在差异效果 Cesium1.47…

做视频怎样传到网站给自己的网站起名字

697. 数组的度 解题思路 首先创建一个IndexMap 键表示元素 值表示一个列表List list存储该元素在数组的所有索引之后再次创建一个map1 针对上面的List 键表示列表的长度 值表示索引的差值遍历indexmap 将所有的list的长度 和 索引的差值存储遍历map1 找到最大的key 那么这个Ke…

常用企业网站模板对比网站数据展示

在Kubernetes环境中,故障排除是管理者日常工作中不可或缺的一部分。随着容器化应用的广泛采用,需要一种高效的方法来诊断和解决Pod内部的问题。本文将重点介绍如何利用抓包技术提升Kubernetes环境中Pod内部故障排除的效率。 为什么需要Pod内抓包 在Kube…

建设一个班级网站的具体步骤网站开发技能证书

洞悉全球汽车产业格局,前瞻业界未来趋势。2023年7月27日-30日,时隔三年,重聚武汉国际博览中心,2023世界汽车制造技术暨智能装备博览会盛大开幕。深耕汽车行业多年的世界汽车制造技术暨智能装备博览会,掀起行业热点新高…

做网站泉州wordpress 前后台都进不去

问题描述 第一种方法 每一行放一个皇后边放皇后边判断是否符合条件递归到第n行&#xff0c;则说明当前方案符合条件&#xff0c;进行遍历 代码实现 #include <cstring> #include <iostream> #include <algorithm>using namespace std;const int N 10;int…

如何申请个人网站怎么把自己的网站推广出去

【SpringBoot实战】基于阿里云实现文件上传 在实际项目开发中&#xff0c;不可避免地会使用到阿里云OSS进行文件存储。尽管阿里云有详细的开发文档&#xff0c;但本篇博客的目的是让我们能够用简明的代码快速实现这个功能。 引入依赖 <dependencies><!-- 阿里云oss…

自己做的网页怎么上传到网站北京网站优化体验

文章目录 一、Tomcat 安装1.1 选择合适的 Tomcat 版本1.2 下载 Tomcat1.3 配置环境变量 二、 运行 Tomcat2.1 启动 Tomcat2.2 关闭 Tomcat 参考资料 一、Tomcat 安装 1.1 选择合适的 Tomcat 版本 Apache Tomcat 是 Jakarta EE&#xff08;正式JavaEE&#xff09;技术子集的开…

做网站多少钱一年二次开发包

今天刷SQL简单查询&#xff0c;大家有兴趣可以刷一下 目录 相关表数据&#xff1a; 题目及思路解析&#xff1a; 总结归纳&#xff1a; 知识补充&#xff1a; 关于LIKE操作符/运算符 LIKE其他使用场景包括 LIKE模糊匹配情况 相关表数据&#xff1a; 1、student_info表 2、sc…

dedecms网站别名解析世界十大软件公司排名

---- 整理自B站UP主 踌躇月光 的视频 1. 全加器 用门电路实现两个二进制数相加并求出和的组合线路&#xff0c;称为一位全加器。一位全加器可以处理低位进位&#xff0c;并输出本位加法进位。全加器比半加器多了一位进位。 1.1 实验 1&#xff1a;通过两个半加器设计全加器 1.…

网站建设合同定义南阳seo长尾关键词

ESP32和ESP8266的ESP-MESH 功能介绍一、介绍ESP-MESH二、安装painlessMesh库三、ESP-MESH基本示例&#xff08;广播消息&#xff09;四、示范 功能介绍 了解如何使用ESP-MESH网络协议通过ESP32和ESP8266 NodeMCU板构建网状网络。 ESP-MESH允许多个设备&#xff08;节点&#x…

公司网站在哪备案logo设计网站官网

https://bbs.espressif.com/viewtopic.php?t75242#p100294 https://blog.csdn.net/ydogg/article/details/72598752

网页设计音乐网站旅游类网站策划建设_

GPT-4 Turbo、功能融合&#xff1a;OpenAI 首个开发者大会回顾 就在昨天 2023 年 11 月 6 日&#xff0c;OpenAI 举行了首个开发者大会 DevDay&#xff0c;即使作为目前大语言模型行业的领军者&#xff0c;OpenAI 卷起来可一点都不比同行差。 OpenAI 在大会上不仅公布了新的 …

网站主题如何自己做资源网站

1.分发简介 RabbitMQ不设置的话默认采用轮询方式分发消息,你一个我一个(公平);但实际生活中,由于处理速度不同,若还采用轮询方式分发会导致处理速度快的空等待,因此我们采用不公平分发 2.不公平分发 在消费者这侧设置即可,以之前的Worker3和Worker4为例 2.1.Worker3 packa…