尼恩说在前面
在45岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团、蚂蚁、得物的面试资格,遇到很多很重要的相关面试题:
问题1:在实际生产环境中,如何给大表加索引?
问题2:给大表加索引会影响业务吗?如何解决?
最近有小伙伴面试阿里,都问到了相关的面试题。小伙伴没有系统的去梳理和总结,所以支支吾吾的说了几句,面试官不满意,面试挂了。
所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V175版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】获取
本文目录
- 尼恩说在前面
- 如何判断什么时候可以给大表加索引?
- 两种索引构建的方式
- 早期DDL操作,属于 离线模式(Offline) 类型
- 早期`copy` VS `inplace` 两种方式的对比
- MySQL5.6.7 之前, 如何在线模式 为大表添加索引?
- 方式一:“影子策略”
-“影子策略”具体实践案例,大致如下:
-“影子策略” 的优点
-“影子策略” 的缺点
- 方案二:pt-online-schema-change 工具
- pt-online-schema-change 工作原理
- pt-online-schema-change 的优点
- pt-online-schema-change 的问题:
- 方案三:MySQL5.6.7 之后的内部 ONLINE DDL
- MySQL5.6.7 Online DDL 的三个阶段
- MySQL5.6.7 Online DDL 如何保证数据 一致性
- DDL 的row log 核心结构row_log_t
- DDL 的 Row Log 记录内容
-与数据恢复和一致性维护的关系
- DDL 的 Row Log (DML增量日志)
-head 、tail 指针 的类型:row_log_buf_t 结构
- 如何追加 Row Log (/DDL期间的DML增量日志)?
- (1)全拷贝 追加 Row Log
- (2)半拷贝 追加 Row Log
- 如何回放 Row Log ,把DDL期间的DML增量日志更新到索引树上的?
- (1)第一种回放:文件扫描/文件回放
- (2)第二种回放:内存扫描/内存回放
- row_log_t 结构体的总结
- 工作流程
- MySQL5.6.7 Online DDL 在线添加索引的案例
- Prepare阶段:
- ddl执行阶段:
- commit阶段
- 1000W级大表 内部 Online DDL 的性能问题:
- 如果一定要在高峰期做online DDL,怎么办?
- 使用外部 online 工具 gh-ost 做online DDL
- gh-ost 和 pt-online-schema-change 的区别
- 尼恩架构团队的塔尖 sql 面试题
- 说在最后:有问题找老架构取经
如何判断什么时候可以给大表加索引?
Waiting for meta data lock
,造成业务崩溃。两种索引构建的方式
在线模式(Online DDL): 这种模式允许在构建索引的同时,数据库可以继续进行读写操作,对业务的影响较小。 但不是所有的存储引擎和数据库版本都支持这种方式。 如果支持,例如在较新的 InnoDB 存储引擎版本中,可以通过设置参数来使用在线模式构建索引。 不过,在线模式可能会消耗更多的系统资源。 离线模式(Offline): 在构建索引时,会对表进行锁定,禁止其他读写操作,直到索引构建完成。 这种方式比较简单直接,但会对业务产生较大的影响。 如果数据库可以承受一段时间的停机或者业务低峰期足够长,离线模式也是一种选择。
关于DDL 和DML , 尼恩给大家 说明一下 DDL(Data Definition Language)即数据定义语言,是用于定义、修改和删除数据库对象(如数据库、表、索引、视图、存储过程等)的 SQL 语句集合。它的主要作用是构建和管理数据库的结构,为存储和操作数据提供框架。 DML(Data Manipulation Language)即数据操作语言,用于对数据库中的数据进行操作。它主要包括 INSERT(插入)、UPDATE(更新)、DELETE(删除)操作,这些操作可以让用户在数据库的表中添加新的数据、修改现有数据的值或者删除不需要的数据。
早期DDL操作,属于 离线模式(Offline) 类型
copy table
和inplace
两种方式,属于 离线模式(Offline), 会对表进行锁定,禁止其他读写操作(DML),直到索引构建完成。copy table
和inplace
两种方式,具体如下:创建与原表相同的临时表,并在临时表上执行DDL语句 锁原表,不允许DML(数据操作语言),允许查询 将原表中数据逐行拷贝至临时表(过程没有排序) 原表升级锁,禁止读写,即原表暂停服务 rename操作,将临时表重命名原表
orders
的表,想要添加一个名为idx_order_date
的索引到order_date
列。ALTER TABLE orders ADD INDEX idx_order_date (order_date), ALGORITHM=COPY;
ALGORITHM=COPY
指定了使用copy table
方式来执行DDL操作。创建frm(表结构定义文件)临时文件 锁原表,不允许DML(数据操作语言),允许查询 根据聚集索引顺序构建新的索引项,按照顺序插入新的索引页 原表升级锁,禁止读写,即原表暂停服务 rename操作,替换原表的frm文件
ALTER TABLE orders ADD INDEX idx_order_date (order_date), ALGORITHM=INPLACE;
早期copy VS inplace 两种方式的对比
Waiting for meta data lock
,造成业务崩溃。copy方式
还是 inplace
方式,数据库表在DDL操作期间不可用,因此被称为“离线模式”。MySQL5.6.7 之前, 如何在线模式 为大表添加索引?
“影子策略” pt-online-schema-change 方案
方式一:“影子策略”
“影子策略”具体实践案例,大致如下:
创建一张与原表结构相同的新表(例如 tb_new
)。在新表上创建索引。 重命名原表为其他表名(例如 tb
重命名为tb_tmp
),新表重命名为原表名(tb_new
重命名为tb
)。为原表( tb_tmp
)新增索引。交换表,新表改回最初的名称( tb
),原表改回最初的名称(tb_tmp
)。把新表数据导入原表(即把新表承担业务期间产生的数据导入到原表中)。
“影子策略” 的优点
“影子策略” 的缺点
方案二:pt-online-schema-change 工具
pt-online-schema-change 工作原理
创建一张与原表结构相同的新表 对新表进行DDL操作(如加索引) 然后在原表上加三个触发器,DELETE/UPDATE/INSERT,将原表中要执行的语句也在新表中执行 将原表数据以数据块(chunk)的形式复制到新表 表交换,原表重命名为old表,新表重命名原表名 删除旧表,删除触发器
pt-online-schema-change 的优点
pt-online-schema-change 的问题:
表要有主键,否则会报错 表不能有trigger 尽管它是尽量减少对业务的影响,但在数据复制和同步阶段仍然会消耗一定的系统资源,包括 CPU、磁盘 I/O 和内存。对于大型表,这个过程可能会比较耗时,并且可能会对数据库的性能产生一定的影响。因此,最好在数据库负载较低的时候使用这个工具。
方案三:MySQL5.6.7 之后的内部 ONLINE DDL
MySQL5.6.7 Online DDL 的三个阶段
Prepare 阶段 执行 提交
Prepare 阶段:
在这个阶段,MySQL 会创建新的临时 frm 文件(与 InnoDB 无关)。 持有 MDL(metadata lock)写锁,禁止读写操作(禁止 DML 和 DDL)。 根据 ALTER TABLE 类型,确定执行方式(copy, online-rebuild, online-no-rebuild)。对于 InnoDB 存储引擎,如果增加的是辅助索引(非主键索引),并且表没有外键约束,MySQL 可以使用 Online-Rebuild 算法。这种方式不需要复制整个表,而是在原表上重建索引,同时允许 DML 操作继续进行 更新数据字典的内存对象。 分配 row_log 对象记录增量DML log(仅 rebuild 类型需要)。 生成新的临时 ibd 文件(仅 rebuild 类型需要)。
降级MDL(metadata lock)写锁 成为 MDL读锁,允许读写操作(允许 DML,禁止 DDL)。 为了保证数据一致性,记录 DDL 执行过程中产生的增量DML log 到 row_log。在这个阶段,与此同时,原表表的所有DML操作日志写入row_log。 扫描原表的聚集索引每一条记录。 遍历新表的聚集索引和二级索引,逐一处理。 根据记录构造对应的索引项。 将构造索引项插入 sort_buffer 块排序。 将 sort_buffer 块更新到新索引树上。 重放 row_log 中的操作到新索引上, 重放该阶段产生的 Row Log日志到新索引树。
当前 Block 为 row_log 最后一个时,禁止读写,升级到MDL(metadata lock)写锁。 重做 row_log 中最后一部分增量。 更新 InnoDB 的数据字典表。 提交事务(刷事务的 redo 日志)。 修改统计信息。 rename 临时 ibd 文件,frm 文件。 变更完成,释放MDL(metadata lock)写锁 。
MySQL5.6.7 Online DDL 如何保证数据 一致性
DDL 的row log 核心结构row_log_t
/** @brief Buffer for logging modifications during online index creation
All modifications to an index that is being created will be logged by
row_log_online_op() to this buffer.
All modifications to a table that is being rebuilt will be logged by
row_log_table_delete(), row_log_table_update(), row_log_table_insert()
to this buffer.
When head.blocks == tail.blocks, the reader will access tail.block
directly. When also head.bytes == tail.bytes, both counts will be
reset to 0 and the file will be truncated. */
struct row_log_t {
int fd; /*!< file descriptor */
ib_mutex_t mutex; /*!< mutex protecting error,
max_trx and tail */
page_no_map *blobs; /*!< map of page numbers of off-page columns
that have been freed during table-rebuilding
ALTER TABLE (row_log_table_*); protected by
index->lock X-latch only */
dict_table_t *table; /*!< table that is being rebuilt,
or NULL when this is a secondary
index that is being created online */
bool same_pk; /*!< whether the definition of the PRIMARY KEY
has remained the same */
const dtuple_t *add_cols;
/*!< default values of added columns, or NULL */
const ulint *col_map; /*!< mapping of old column numbers to
new ones, or NULL if !table */
dberr_t error; /*!< error that occurred during online
table rebuild */
trx_id_t max_trx; /*!< biggest observed trx_id in
row_log_online_op();
protected by mutex and index->lock S-latch,
or by index->lock X-latch only */
row_log_buf_t tail; /*!< writer context;
protected by mutex and index->lock S-latch,
or by index->lock X-latch only */
row_log_buf_t head; /*!< reader context; protected by MDL only;
modifiable by row_log_apply_ops() */
ulint n_old_col;
/*!< number of non-virtual column in
old table */
ulint n_old_vcol;
/*!< number of virtual column in old table */
const char *path; /*!< where to create temporary file during
log operation */
};
一类是增加索引类,即调用row_log_online_op函数来进行dml操作缓存填写; 一类是其他ddl。则调用row_log_table_delete, row_log_table_update, row_log_table_insert进行缓存区填充。
fd,path :分别是在ddl操作期间,用于保存dml操作记录的临时文件的句柄和文件名;从源码可以看到该目录为innodb_tmpdir指定,若该值为空,则设置为tmpdir对应目录。其获取临时目录的函数为innobase_mysql_tmpdir() blobs:记录的写入是按照记录块的方式,该字段表示记录块的数量; table:不为null表示重建表,为null表示online 添加索引 tail,head:该成员就是记录块,分别用于写入和回放。具体结构 row_log_buf_t 下面会详细说明
DDL 的 Row Log 记录内容
操作类型记录
数据行标识记录
数据值记录 对于插入操作,会记录插入的完整数据行内容。对于更新操作,会记录更新前和更新后的列值。 例如,记录 “更新前 customer_name 的值为 'John',更新后的值为 'Johnny'”。 对于删除操作,会记录被删除行的数据内容或者关键标识,以便在需要时可以恢复或审计这些数据。
DDL 操作开始前的日志记录开启 当检测到即将进行 DDL 操作时,数据库会开启专门的日志记录机制来捕获 DML 操作的 Row Log。这个机制可能会涉及分配内存缓冲区来暂存日志记录,或者直接将日志记录指向现有的重做日志(redo log)和撤销日志(undo log)系统(如在 InnoDB 存储引擎中)。 DDL 操作期间的日志记录 在 DDL 操作过程中,如修改表结构,所有对该表的 DML 操作的 Row Log 都会被持续记录。例如,在将一个表的列数据类型从整数改为字符串的过程中,如果有新的数据行插入,日志会记录插入操作的详细信息;如果有数据行更新,会记录更新前后的数据值和列信息。这些日志记录是按照时间顺序或者操作顺序进行存储的,以保证可以准确地还原操作过程。 DDL 操作完成后的日志处理 当 DDL 操作完成后,数据库会根据记录的 Row Log 来处理受影响的数据行。如果是添加列的 DDL 操作,会根据日志中的插入和更新操作记录,为新列填充合适的值或者更新数据行的结构。例如,通过日志中记录的插入操作,为新添加列填充默认值;通过更新操作记录,按照新的列结构调整数据行。如果是修改列的数据类型的 DDL 操作,会根据日志中的更新操作记录,对数据行中的列值进行转换或者重新组织,确保数据行与新的表结构相匹配。
与数据恢复和一致性维护的关系
数据恢复场景 在 DDL 操作过程中,如果系统出现故障(如崩溃、断电等),记录 DML 操作的 Row Log 可以用于数据恢复。数据库可以在重启后,根据日志中的信息来恢复在 DDL 操作期间未完成的 DML 操作,确保数据的完整性。例如,在故障发生前,有部分数据行已经完成插入操作但未在 DDL 操作后进行相应的处理,通过日志可以重新执行这些处理步骤。 数据一致性维护 通过记录和处理这些 Row Log,可以避免在 DDL 操作过程中由于数据行的变化而导致的数据不一致问题。例如,在修改表结构的同时,确保新插入的数据行能够符合新的结构要求,更新的数据行能够正确地转换列值。这样,无论是在正常的操作过程中还是在出现异常情况后,都能够维护数据库数据的一致性。
DDL 的 Row Log (DML增量日志)
操作flag + 事务id + 操作记录
0x61 + 100 + <1000, ‘技术自由圈’, 40>
0x61
代表类型,这是一个插入操作。操作flag包含两种:INSERT和DELETE,100
表示这个操作的事务id,<1000, ‘技术自由圈’, 1>
表示操作的记录。
一个记录块可保存一条或多条DML log 增量日志。 一条增量DML 日志可能跨 2个 bock 记录块。
最前面两个Log, 存在第一个Block中 第3个Log的前半部分,存在第一个Block中,第3个Log的后半部分,存在第二个Block中 第4个Log, 存在第二个Block中 第5个Log的前半部分,存在第二个Block中,第5个Log的后半部分,存在第三个Block中 第6个Log, 存在第三个Block中
index_height
对应的Row Log。Log 日志: 表示DML操作日志,它的结构为 操作flag + 事务id + 操作记录
。head 指针: head 指针 指向第一个没有处理(/没有回放)的Bock 块。 在新的索引树 建好了之后,这些增量DML log 都需要 进行回放,把数据更新到新的索引树。 head 指针 就是指向需要回放的 Bock 块,回放完成一个Bock 块 里边的 DML Log后,head指针向后移到下一个Bock 块。 如上图,回放的时候,从Block的head 头部开始扫描,head指针在回放前 的 第一个Block的位置。 tail 指针: 从名字上看,tail相当于当前日志的尾部,head相当于当前日志的头部。回放时,head的位置不超过tail。 这是用于将DML操作写入一个Block时,用来定位Block中Log插入位置的指针,插入完一个Block,tail指针向后移动到新插入的Block。 如下图,Block的尾部插入Log,所以,在插入后,tail指针 处在的最后一个Block位置。
内存日志: 内存中会存放一个总大小等于 inndob_sort_buffer_size
的Block,用于写入DML log 操作文件日志: 当内存中大小大于 innodb_sort_buffer_size
, 内部中 Block写满, 写满的Block会刷到磁盘上,空出内存中的Block给后续的Log写入 日志文件中,所有Block总大小如果超过 innodb_online_alter_log_max_size
,写入就会报错
node1-performance_schema>show variables like "%innodb_sort_buffer_size%";
+-------------------------+---------+
| Variable_name | Value |
+-------------------------+---------+
| innodb_sort_buffer_size | 1048576 |
+-------------------------+---------+
1 row in set (0.01 sec)
head 、tail 指针 的类型:row_log_buf_t 结构
/** Log block for modifications during online ALTER TABLE */
struct row_log_buf_t {
byte *block; /*!< file block buffer */
ut_new_pfx_t block_pfx; /*!< opaque descriptor of "block". Set
by ut_allocator::allocate_large() and fed to
ut_allocator::deallocate_large(). */
mrec_buf_t buf; /*!< buffer for accessing a record
that spans two blocks */
ulint blocks; /*!< current position in blocks */
ulint bytes; /*!< current position within block */
ulonglong total; /*!< logical position, in bytes from
the start of the row_log_table log;
0 for row_log_online_op() and
row_log_apply(). */
};
byte *block
:这是一个指向字节( byte
)类型的指针, 用于存储文件块的缓冲区。这个缓冲区用于存储在ALTER TABLE操作中被修改的数据块。block 的大小由参数innodb-sort-buffer-size决定,默认为1MB。 在处理在线 ALTER TABLE
操作过程中的修改日志时,这个缓冲区可能用于暂存从文件(可能是数据库文件等)读取出来的块数据,或者是准备写入文件的块数据。ut_new_pfx_t block_pfx
:这是一个不透明的描述符( opaque descriptor
),用于描述block
。这个描述符的作用 是帮助内存分配器正确地管理和释放block
所指向的内存块。它可能包含了关于内存块大小、分配来源等信息,使得内存管理系统能够准确地处理这个内存块。具体实现和用途依赖于底层的内存分配器。mrec_buf_t buf
:这是一个用于访问跨越两个块的记录的缓冲区。 一条记录有可能因为大小原因,跨越了两个物理存储块。这个 buf
缓冲区就是为了方便处理这种情况而设置的。它可能包含了一些机制,用于正确地拼接和访问跨越两个块的记录数据,例如记录了两个块的位置信息以及如何将它们组合在一起的规则。ulint blocks
blocks:当block空间使用完,会将block的数据写入临时文件中,再次利用block的空间。blocks字段用于记录当前处理的block的个数。 ulint bytes
bytes: 用于记录当前block内已经使用的字节数。 ulonglong total
:代表逻辑位置,是以字节为单位从 row_log_table
日志开始计算的。这个变量提供了一种全局的位置参考,使得在处理日志数据时,可以清楚地知道当前操作的位置相对于整个日志文件的起始位置的偏移量。
如何追加 Row Log (/DDL期间的DML增量日志)?
index_height
索引的Row Log为例来说明。(1) 全拷贝 追加 Row Log
innodb_sort_buffer_size
(默认1M)大小的Block,tail指针指向这个新的row_log_buffer 。剩余空间 = innodb_sort_buffer_size(默认1M) - 当前Block中已有Log的总大小
<0x61 + 3355 + <25, ‘技术自由圈’, 1>>
完整拷贝到末尾Log。简历神僧 、45岁老架构师尼恩特别提示 , 以上的图是对 mysql 源码的 一大致的梳理,并没有代码级正确,主要目标是为了方便大家理解。 具体的源码细节过于复杂,可能和尼恩的 梳理有 出入, 细节以源码为准。 再啰嗦一下,由于网上其他文章在这个复杂的场景, 写的过于复杂,不好理解。尼恩在这里给大家梳理的 是一个大致的 全拷贝的 流程。并不是代码级正确, 但是这个不妨碍大家的理解。 如果觉得过于复杂, 后面尼恩会在 《尼恩Java面试宝典》配套视频中,做出细致解读。
(2) 半拷贝 追加 Row Log
剩余空间 = innodb_sort_buffer_size(默认1M) - 当前Block中已有Log的总大小
一半 全部拷贝到row_log_buffer 末尾, 然后把row_log_buffer 里边的内容刷入 row_log_file,清空row_log_buffer 再把另一半拷贝清理后的 row_log_buffer 。
<0x61 + 3355 + <25, ‘技术自由圈’, 1>>
的前半部分,拷贝到row_log_buffer 的末尾Log,<0x61 + 3355 + <25, ‘技术自由圈’, 1>>
的后半部分拷贝到row_log_buffer 的头部。简历神僧 、45岁老架构师尼恩特别提示 , 以上的图是对 mysql 源码的 一大致的梳理,并没有做到 代码级正确,主要目标是为了方便大家理解。 再啰嗦一下,由于网上其他文章在这个复杂的场景, 写的过于复杂,不好理解。具体的源码细节过于复杂,可能和尼恩的 梳理有 出入, 所以实现的细节以源码为准。 尼恩在这里给大家梳理的 是一个大致的半拷贝的 流程。并不是代码级正确, 但是这个不妨碍大家的理解。如果觉得过于复杂, 后面尼恩会在 《尼恩Java面试宝典》配套视频中,做出细致解读。
如何回放 Row Log ,把DDL期间的DML增量日志更新到索引树上的?
index_height
为例,我们再来看下Row Log中的日志是如何更新到索引树的?index_height
索引的Row Log 回放为例来说明。(1)第一种回放:文件扫描/文件回放
index_height
。index_height
的第最后个叶子节点。index_height
,至此,Row Log file文件清空。(2)第二种回放:内存扫描/内存回放
index_height
,内存扫描/内存回放 动作结束。row_log_t 结构体的总结
row_log_t
对象来记录这些变更。这个对象会用于缓存DML操作日志,直到DDL操作完成。工作流程
日志块分配: 在在线DDL操作期间,InnoDB会为 row_log_t
分配一个日志块(block
),用于存储DML操作的日志记录。日志记录: 所有对表的DML操作(如INSERT、UPDATE、DELETE)都会被记录在 row_log_t
的日志块中。这些日志记录了行级别的变更,以确保DDL操作完成后,这些变更能够被应用到新的表结构上。日志回放: 在DDL操作完成后, row_log_t
中的日志会被重放,将DML操作应用到修改后的表结构上。这个过程确保了数据的一致性和完整性。性能优化: row_log_t 的使用是InnoDB在线DDL操作性能优化的一部分。通过记录DML操作日志,InnoDB可以减少DDL操作对业务的影响,同时保持数据的完整性。 临时文件管理: row_log_t
中的日志块数据被存储在临时文件中,这个临时文件的大小由参数innodb_online_alter_log_max_size
控制,默认值为128M。如果这个参数设置的值不够大,可能会导致DDL操作失败。
row_log_t
结构体是InnoDB在线DDL操作中记录和应用DML操作日志的关键组件,它帮助InnoDB存储引擎在修改表结构的同时,记录和回放对表数据的变更,确保数据的一致性和完整性。MySQL5.6.7 Online DDL 在线添加索引的案例
Prepare阶段:
创建新的临时frm文件 持有EXCLUSIVE-MDL锁,禁止读写 根据alter类型,确定执行方式(copy,online-rebuild,online-norebuild), 新增二级索引使用的 是online-rebuild, 更新数据字典的内存对象,标记test_user表所有索引 online_status
为ONLINE_INDEX_CREATION
,表示该表索引都处在在线DDL状态。分配row_log对象记录增量 dml log 生成新的临时ibd文件,根据旧表test_user的ibd文件,创建副本ibd文件
ddl执行阶段:
降级EXCLUSIVE-MDL锁,允许读写 扫描old_table的聚集索引每一条记录 rec,并且遍历新表的聚集索引和二级索引,逐一处理
根据rec构造对应的索引项 将构造索引项插入sort_buffer块 将sort_buffer块进行归并排序, 插入新的索引树
commit阶段
升级到EXCLUSIVE-MDL锁,禁止读写 重做最后row_log中最后一部分增量 更新innodb的数据字典表 提交事务(刷事务的redo日志) 修改统计信息 rename副本ibd文件和frm文件为旧表名,即原test_user表的frm和ibd文件名 变更完成
1000W级大表 内部 Online DDL 的性能问题:
CPU开销大
磁盘开销大
buffer pool
又满了,触发刷脏行为,这时就会出现查询请求等待刷脏结束,查询响应变慢。评估表中的数据量 观察MySQL的CPU使用率
目的 | 解决方法 |
如果一定要在高峰期做online DDL,怎么办?
使用外部 online 工具 gh-ost 做online DDL
gh-ost
是一个用于 MySQL 数据库的无阻塞在线表结构迁移工具,gh-ost
和pt-online-schema-change
工具的功能类似,主要目的是在不中断或尽量少中断数据库服务的情况下进行表结构的变更,它是基于 MySQL 的复制协议(replication protocol)来工作的。创建幽灵表(Ghost Table) 当启动 gh-ost
工具进行表结构变更时,首先会创建一个幽灵表。这个幽灵表的结构是按照修改后的表结构定义来创建的,例如,要添加新列或者修改列的数据类型等变更都会体现在幽灵表的结构中。 数据迁移与同步 接着, gh-ost
会利用 MySQL 的二进制日志(bin-log)来跟踪原表的更改。它通过解析二进制日志,将原表的插入、更新和删除操作应用到幽灵表上,从而实现数据的迁移和同步。这是一种异步的过程,原表的操作不会被阻塞,能够正常进行读写操作。 在数据迁移过程中, gh-ost
还会对原表进行分块(chunking)处理。它将原表的数据分成多个小块,逐块地将数据从原表迁移到幽灵表,这种方式有助于控制内存和磁盘 I/O 的使用,避免一次性处理大量数据带来的性能问题。 切换表操作 当幽灵表的数据和原表的数据基本同步完成后, gh-ost
会进行一个切换操作。这个切换操作是通过原子性地重命名表来实现的,将原表重命名为一个中间表,然后将幽灵表重命名为原表的名称。 这样,数据库的读写操作就会自动切换到新的表结构上,完成表结构的在线修改。
高效的数据迁移和同步 利用 MySQL 二进制日志来跟踪和同步数据的方式使得 gh-ost
能够高效地处理数据迁移。这种方式对于高并发的数据库环境特别有效,因为原表的读写操作不会被长时间阻塞,数据的更新能够及时地在幽灵表中得到反映。 灵活的分块策略 分块处理数据的策略使得 gh-ost
能够适应不同大小的表。对于大型表,它可以通过合理地划分数据块,逐步完成数据迁移,从而减少对系统资源的瞬间冲击。 例如,对于一个有千万行数据的表,可以将其分成若干个较小的数据块,每次迁移一块数据,这样可以在不影响数据库正常运行的情况下完成数据迁移。 对数据库性能影响小 由于采用了异步迁移和分块处理等技术, gh-ost
在整个表结构变更过程中对数据库性能的影响相对较小。在数据迁移阶段,原表可以正常读写,只是在切换表的瞬间可能会有短暂的影响,但这种影响通常是可以接受的,特别是在数据库负载较高的情况下,这种优势更加明显。
对二进制日志的依赖 gh-ost
高度依赖 MySQL 的二进制日志来跟踪原表的操作。如果二进制日志的配置不正确或者出现问题,例如,二进制日志损坏或者没有开启二进制日志,那么 gh-ost
将无法正常工作。因此,在使用 gh-ost
之前,需要确保二进制日志的正确配置和完整性。复杂环境下的风险 在复杂的数据库环境中,如存在大量的存储过程、视图、外键约束等, gh-ost
可能会遇到一些问题。虽然它在尽力减少对这些复杂结构的影响,但在某些情况下,仍然可能会导致数据不一致或者操作失败。 例如,在处理涉及多个表的外键关系时,需要特别小心,确保表结构变更后外键关系仍然正确。 资源消耗和性能优化 尽管 gh-ost
对性能的影响相对较小,但在数据迁移过程中仍然会消耗一定的系统资源,包括 CPU、磁盘 I/O 和内存。对于资源紧张的数据库系统,需要仔细评估和优化 gh-ost
的使用,例如,合理调整分块大小和迁移速度等参数,以达到最佳的性能和资源利用效果。
gh-ost 和 pt-online-schema-change 的区别
gh-ost:不使用触发器,而是通过解析binary log来捕获对原表的更改,并将这些更改应用到ghost表中。这种方法使得gh-ost对迁移过程有更多的控制,可以真正暂停迁移过程,并且可以将迁移的写负载与主工作负载分离。 pt-online-schema-change:使用触发器来捕获对原表的更改,并将这些更改复制到新表中。这种方式在某些情况下可能会导致性能问题,尤其是在高并发的环境中。
gh-ost:采用异步方式,通过解析binary log来异步地将变更应用到ghost表,这可能会增加网络流量,并且要求使用row-based replication以保证数据一致性。 pt-online-schema-change:采用同步方式,通过触发器同步地将变更应用到新表,这种方式可能会在高负载下影响性能。
gh-ost:由于不使用触发器,可以减少对复制的影响,并且可以更容易地暂停和恢复迁移过程。 pt-online-schema-change:使用触发器可能会增加主从延迟的风险,尤其是在高负载下。
gh-ost:提供了动态控制功能,允许用户在迁移过程中根据MySQL的指标动态调整迁移行为,例如设置线程运行的阈值。 pt-online-schema-change:也提供了负载控制功能,但可能需要在迁移过程中停止并重新配置以调整负载,这可能会增加操作的复杂性。
gh-ost:不支持外键和触发器,也不支持Galera Cluster,因为它使用LOCK TABLE进行表切换,这与Galera不兼容。 pt-online-schema-change:支持更多的场景,包括带有外键和触发器的表,以及Galera Cluster。
尼恩架构团队的塔尖 sql 面试题
sql查询语句的执行流程:
索引
索引下推 ?
索引失效
MVCC
binlog、redolog、undo log
mysql 事务
分布式事务
mysql 调优
说在最后:有问题找老架构取经
空窗1年/空窗2年,如何通过一份绝世好简历, 起死回生 ?
空窗8月:中厂大龄34岁,被裁8月收一大厂offer, 年薪65W,转架构后逆天改命!
空窗2年:42岁被裁2年,天快塌了,急救1个月,拿到开发经理offer,起死回生
空窗半年:35岁被裁6个月, 职业绝望,转架构急救上岸,DDD和3高项目太重要了
空窗1.5年:失业15个月,学习40天拿offer, 绝境翻盘,如何实现?
100W 年薪 大逆袭, 如何实现 ?
100W案例,100W年薪的底层逻辑是什么? 如何实现年薪百万? 如何远离 中年危机?
如何 评价一份绝世好简历, 实现逆天改命,包含AI、大数据、golang、Java 等
实现职业转型,极速上岸
关注职业救助站公众号,获取每天职业干货
助您实现职业转型、职业升级、极速上岸
---------------------------------
实现架构转型,再无中年危机
关注技术自由圈公众号,获取每天技术千货
一起成为牛逼的未来超级架构师
几十篇架构笔记、5000页面试宝典、20个技术圣经
请加尼恩个人微信 免费拿走
暗号,请在 公众号后台 发送消息:领电子书
如有收获,请点击底部的"在看"和"赞",谢谢