为什么MySQL主从数据表文件大小会相差10倍


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


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

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 是否在这之中,如果在不可以见,反之可见
 }

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

  • https://www.jianshu.com/p/8c2c359d3e3f

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的工作线程:

c66fd4a23baf59137b2b71a99d4403c7.png
image.png

并且purge线程状态处于running状态,如下:1ee0294652c4aef2c23c982204eacdbc.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)

具体参考如下:

  • https://bugs.mysql.com/bug.php?id=107426


《深入浅出MGR》视频课程

戳此小程序即可直达B站

https://www.bilibili.com/medialist/play/1363850082?business=space_collection&business_id=343928&desc=0



文章推荐:


想看更多技术好文,点个“在看”吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值