美文网首页
MySQL:主从表大小相差巨大和一个BUG

MySQL:主从表大小相差巨大和一个BUG

作者: 重庆八怪 | 来源:发表于2022-06-26 17:21 被阅读0次

水平有限,如有误请谅解。


一、主从相同表空间相差巨大

1.1 问题描述

我们知道MySQL主从基本上是逻辑的复制,那么有少量的空间差异没有问题,但是本案例主库表只有10G,但是从库表有100G,这么大的差距比较少见,需要分析原因。

1.2 问题分析

实际上这个问题还是要从read view和purge 线程2个方面进行分析,不得不在老生常谈一下(复制一下以前的文章)。

1.2.1 read view和可见性简述

一致性读取(consistent read),根据隔离级别的不同,会在不同的时机建立read view,如下:

  • RR 事务的第一个select命令发起的时候建立read view,直到事务提交释放
  • RC 事务的每一个select都会单独建立read view

有了read view 就能够对每行数据的可见性进行判断了,下面是read view中的关键属性

  • m_up_limit_id:如果行的trx id 小于了m_up_limit_id则可见。
  • m_low_limit_id:如果行的trx id 大于了m_low_limit_id则不可见。
  • m_ids:是用于记录建立read view时刻的读写事务的vector数组,用于对于m_up_limit_id和m_low_limit_id之间的trx需要根据它来进行判定,是否处于活跃状态。
  • m_low_limit_no则用于记录建立read view时刻的最小trx no,主要用于purge线程判断清理undo使用。

而对于可见性的判断我们可以参考如下函数:

/** 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. */
    bool changes_visible(
        trx_id_t        id,
        const table_name_t& name) const
        MY_ATTRIBUTE((warn_unused_result))
    {
        ut_ad(id > 0);
        if (id < m_up_limit_id || id == m_creator_trx_id) { //小于 可见
            return(true);
        }
        check_trx_id_sanity(id, name);
        if (id >= m_low_limit_id) { //大于不可见
            return(false);
        } else if (m_ids.empty()) { //如果之间的 active 为空 则可见 
            return(true);
        }
        const ids_t::value_type*    p = m_ids.data();
        return(!std::binary_search(p, p + m_ids.size(), id)); //否则比较本trx id 是否在这之中,如果在不可以见,反之可见
    }

当然对于主键和二级索引的可见性判断的方式是不一样的,可以参考:

1.2.2 purge线程主要的工作

总的说来purge线程主要做的工作如下:

  • 清理del flag标签的记录
  • 清理undo的历史版本
  • 如果需要进行undo tablespace截断。

purge线程总会积压一段时间才会进行History list length的清理,如果是小事务(每次修改的page小于innodb_purge_batch_size的设置),那么需要128个这种小事务才清理一次,如果是大事务那么修改两超过了(innodb_purge_batch_size*innodb_purge_rseg_truncate_frequency)的设置则进行一次清理,但是不管如何History list length这个指标持续不为0是正常。
需要注意的是如果长查询结束的时候可能会集中进行purge,purge各个线程可能满负荷工作,这个带来的压力也是不小的,因为purge需要离散的读取各个block做清理工作。如下,9281为一个purge的工作线程:

image.png

并且purge线程状态处于running状态,如下:


image.png
1.2.3 它们和长查询的关系

假设我们开启了一个select语句,这个语句在不断的查询期间,需要根据行头的trx_id(只考虑主键)和read view去判定扫描到数据的可见性,如果判定为不可见则需要通过行头另外一个信息undo_ptr去构建这行数据的前版本,如果还不可见则继续构建,知道可见为止,然后输出给MySQL上层。
打个比方在select期间执行了大量的数据更改删除(注意可以和select表不是一个表),select 语句扫描出一行带有del flag标签的数据就可以根据上面说的方法构建它的前版本,如果这个时候这些del flag的数据被pruge线程清理了,并且清理了undo信息,那么则没有构建前版本的依据了。
因此我们在做长select的时候,del flag的记录需要保留在segment内部,不能清理,不能重用。当然undo也不能清理。

1.2.4 综合分析

我们知道我们的从库一般来讲都用作读任务,如果有长查询正在进行,那么由于del flag记录不能被清理,所以导致了从库表不断膨胀,同时undo也不能清理掉,也会膨胀。这也就照成了主从库表大小相差巨大的显现。
对于这种现象我们可以在从库的show engine的History list length中确认,实际上History list length 就是当一个update undo log (非insert)的计数器,一个事务只有一个undo log 。来源为trx_sys->rseg_history_len,这个值会在事务提交的时候更新,无论事务大小。但是由于很多内部事务的存在,这个值会远大于可观测的事务个数。栈如下:

#0  trx_purge_add_update_undo_to_history (trx=0x7fffeac7df50, undo_ptr=0x7fffeac7e370, undo_page=0x7fff2837c000 "\373\252\223T", update_rseg_history_len=true, n_added_logs=1, 
    mtr=0x7fffe8399830) at /home/mysql/soft/percona-server-5.7.29-32/storage/innobase/trx/trx0purge.cc:354
#1  0x0000000001b9c064 in trx_undo_update_cleanup (trx=0x7fffeac7df50, undo_ptr=0x7fffeac7e370, undo_page=0x7fff2837c000 "\373\252\223T", update_rseg_history_len=true, n_added_logs=1, 
    mtr=0x7fffe8399830) at /home/mysql/soft/percona-server-5.7.29-32/storage/innobase/trx/trx0undo.cc:1970
#2  0x0000000001b8b639 in trx_write_serialisation_history (trx=0x7fffeac7df50, mtr=0x7fffe8399830) at /home/mysql/soft/percona-server-5.7.29-32/storage/innobase/trx/trx0trx.cc:1684
#3  0x0000000001b8c9b0 in trx_commit_low (trx=0x7fffeac7df50, mtr=0x7fffe8399830) at /home/mysql/soft/percona-server-5.7.29-32/storage/innobase/trx/trx0trx.cc:2184

如果查询期间History list length很大则可能遇到了这种情况。

二、checksum table BUG

最近遇到这个bug 同样的数据同样的row format,进行checksum table得到的结果不一致,当然这是非常特殊的情况,一般不会遇到的,测试如下:

8.0.28

mysql> create table myt2(id int );
Query OK, 0 rows affected (0.12 sec)

mysql> insert into myt2 values(10);
Query OK, 1 row affected (0.01 sec)

mysql> insert into myt2 values(20);
Query OK, 1 row affected (0.00 sec)

mysql> insert into myt2 values(30);
Query OK, 1 row affected (0.00 sec)

mysql> checksum table myt2;
+--------------+------------+
| Table        | Checksum   |
+--------------+------------+
| test517.myt2 | 2306435627 |
+--------------+------------+
1 row in set (0.00 sec)

mysql> alter table myt2 row_format=dynamic;
Query OK, 0 rows affected (0.10 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> checksum table myt2;
+--------------+------------+
| Table        | Checksum   |
+--------------+------------+
| test517.myt2 | 3470396603 |
+--------------+------------+
1 row in set (0.00 sec)

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.28    |
+-----------+
1 row in set (0.00 sec)

具体参考如下:

相关文章

网友评论

      本文标题:MySQL:主从表大小相差巨大和一个BUG

      本文链接:https://www.haomeiwen.com/subject/mrqovrtx.html