zoukankan      html  css  js  c++  java
  • MySQL OSC(在线更改表结构)原理

    1 OSC介绍

    在我们的数据库操作中,更改表结构是一个常见的操作,而当我们的表数据量非常大时,我们更改表结构的时间是非
    常的长,并且在跟改期间,会生成一个互斥锁,阻塞对整个表的所有操作,这样,对于我们线上数据来说是无法容忍
    的,以往的做法中,为了不影响线上业务,我们一般采用:先在线下从库更改表结构,然后替换线上从库,这样一台
    台的修改,最后做一下主库切换,这个过程会耗费很长时间,并且在做主库切换时,风险也非常的大,OSC(Online 
    Schema Change)大多都是利用了触发器的原理,实现了在线更改表结构的同时,避免了锁表,同时还允许其他的dml操
    作,目前已经有多种工具实现了 OSC 下面就几种常见的的工具。
    

    2 MySQL5.6 OnlineDDL

    MySQL5.6 Online DDL可以做到DDLDMLSELECT同时进行

    示例

    alter table test add name varchar(10),ALGORITHM=INPLACE ,LOCK=NONE;
    
    #Locking Options for Online DDL
    LOCK=DEFAULT
    LOCK=NONE
    LOCK=SHARED
    LOCK=EXCLUSIVE
    
    #Performance of In-Place versus Table-Copying DDL Operations
    ALGORITHM=DEFAULT
    ALGORITHM=INPLACE
    ALGORITHM=COPY
    

    实现细节

    #Prepare阶段
    1.创建临时frm文件
    2.持有EXCLUSIVE-MDL锁,禁止读写
    3.根据ALTER类型,确定执行方式(copy,online-rebuild,online-norebuild)
    4.更新数据字典的内存对象
    5.分配row_log对象记录增量
    6.生成临时ibd文件
    
    #ddl执行阶段
    1.降级EXCLUSIVE-MDL锁,允许读写
    2.扫描原表的聚簇索引每条记录
    3.遍历新表的聚簇索引和二级索引,逐一处理
    4.根据记录构造对应的索引项
    5.将构造索引项插入sort_buffer块
    6.将sort_buffer块插入新的索引
    7.处理ddl执行过程中产生的增量(仅rebuild类型需要)
    
    #commit阶段
    1.升级到EXCLUSIVE-MDL锁,禁止读写
    2.应用最后row_log中产的日志
    3.更新innodb的数据字典表
    4.提交事务(刷事务的redo日志)
    5.修改统计信息
    6.rename临时idb文件,frm文件
    7.变更完成
    

    从上面可以看到 在开始 和 结束阶段 还是锁表了 只是缩短了锁表的时间

    以加索引为例,介绍 copy方式跟inplace方式的实现流程
    #copy方式
    1.新建带索引(主键索引)的临时表
    2.锁原表,禁止DML,允许查询
    3.将原表数据拷贝到临时表
    4.禁止读写,进行rename,升级字典锁
    5.完成创建索引操作

    #inplace方式
    1.创建索引(二级索引)数据字典
    2.加共享表锁,禁止DML,允许查询
    3.读取聚簇索引,构造新的索引项,排序并插入新索引
    4.等待打开当前表的所有只读事务提交
    5.创建索引结束
    

    3 Percona公司的pt-online-schema-change

    示例

    pt-online-schema-change h=*,u=* p=**,P=* ,D=enk,t=my1 --alter "add is_sign_1 int(11) unsigned NOT NULL DEFAULT '0'" --drop-old-table [--sleep 10] --print --executeD=lots,t=t_o_tr

    实现细节

    1. 新建tmp_table,表结构同原表 
    CREATE TABLE `$db`.`$tmp_tbl` LIKE `$db`.`$tbl`" 
    
    2. 在tmp_table上更改表结构为需要的表结构
    
    3. 在原表上建立三个触发器,如下:
     #delete 触发器
     CREATE TRIGGER mk_osc_del AFTER DELETE ON $table " "FOR EACH ROW "
     "DELETE IGNORE FROM $new_table ""WHERE $new_table.$chunk_column = OLD.$chunk_column";
     #insert 触发器
     CREATE TRIGGER mk_osc_ins AFTER INSERT ON $table " "FOR EACH ROW "
     "REPLACE INTO $new_table ($columns) "  "VALUES($new_values)";
     #update 触发器
     CREATE TRIGGER mk_osc_upd AFTER UPDATE ON $table " "FOR EACH ROW "
     "REPLACE INTO $new_table ($columns) " "VALUES ($new_values)";
     
     #我们可以看到这三个触发器分别对应于INSERT、UPDATE、DELETE三种操作,
     mk_osc_del,DELETE操作,我们注意到DELETE IGNORE,当新有数据时,我们才进行操作,也就是说,当在后续导入过程中,如果删除
     的这个数据还未导入到新表,那么我们可以不在新表执行操作,因为在以后的导入过程中,原表中改行数据已经被删除,已经没有数据,那
     么他也就不会导入到新表中;
    
     mk_osc_ins,INSERT操作,所有的INSERT INTO全部转换为REPLACE INTO,为了确保数据的一致性,当有新数据插入到原表时,如果
     触发器还未把原表数据未同步到新表,这条数据已经被导入到新表了,那么我们就可以利用replace into进行覆盖,这样数据也是一致的。
     
     mk_osc_upd  UPDATE操作,所有的UPDATE也转换为REPLACE INTO,因为当跟新的数据的行还未同步到新表时,新表是不存在这条记录
     的,那么我们就只能插入该条数据,如果已经同步到新表了,那么也可以进行覆盖插入,所有数据与原表也是一致的;
    
     #我们也能看出上述的精髓也就这这几条replace into操作,正是因为这几条replace into才能保证数据的一致性
    
         
     4. 拷贝原表数据到临时表中,在脚本中使用如下语句
      INSERT IGNORE INTO $to_table ($columns) "  "SELECT $columns FROM $from_table " "WHERE ($chunks->[$chunkno])",
      我们能看到他是通过一些查询(基本为主键、唯一键值)分批把数据导入到新的表中,在导入前,我们能通过参数--chunk-size对每次
      导入行数进行控制,已减少对原表的锁定时间,并且在导入时,我们能通过—sleep参数控制,在每个chunk导入后与下一次chunk导入开
      始前sleep一会,sleep时间越长,对于磁盘IO的冲击就越小
        
    5. Rename 原表到old表中,在把临时表Rename为原表
       RENAME TABLE `$db`.`$tmp_tbl` TO `$db`.`$tbl` ; 
       在rename过程,其实我们还是会导致写入读取堵塞的,所以从严格意思上说,我们的OSC也不是对线上环境没有一点影响,但由于
       rename操作只是一个修改名字的过程,也只会修改一些表的信息,基本是瞬间结束,故对线上影响不太大
    
    6. 清理以上过程中的不再使用的数据,如OLD表
    

    以上即为整个Percona OSC的过程,我们看到精华部分就触发器那一块,不过还有很多细节我未介绍,如:外键、记录binlog(默认情况是不记录binlog的)等等

    由于环境的复杂性,此工具还是有很多风险,如以下几个方面问题或者需要规避的一些问题:
    1. 此工具不是原子操作,如果某一点失败,不仅仅会留下很多中间过程的垃圾文件,而这些文件很难完全清理,并且如果有这些文件存在,
    那么就不能在次执行OSC操作;
    2.在执行时,尽量避免有这个表的批量更新、锁表、优化表的操作,我们能想象的到,如果有锁表、优化表那么OSC是否还能正常执行?
    3.如果存在主从结构,那么尽量在从库先执行,因为如果在主库执行完毕后在到从库执行,我们能想象,主库字段多同步到从库,会不会有问题呢?
    4.必须是单一列的主键或者单一唯一键,这样我们在insert select *from分片时,是不是能更好的处理量呢?
    5.不要有外键,尽管脚本经过严格测试,但是是否还有bug,也未知,表的外键是不是会带来更多的问题呢?
    6.在执行之前,我们是不是要对磁盘容量进行评估呢?因为OSC会使用表的一倍以上空间。

    **以上列到的,只是部分问题,我想如果需要在线进行实施,还需要经过严格的测试,但是它的实现为我们提供了一个很好的在线更改表结构 方法,我相信只要我们能很好的规避他的弊端,它会给我们带来很大的帮助;
    **

    PS:
    使用 pt-osc 修改主键时注意:
    原表上有个复合主键,现在要添加一个自增 id 作为主键,如何进行?
    会涉及到以下修改动作:

     1.删除复合主键定义
     2.添加新的自增主键 3.原复合主键字段,修改成唯一索引
    

    需要将删除原主键、增加新主键和增加原主键为唯一键同时操作:

    alter "DROP PRIMARY KEY,add column pk int auto_increment primary key,add unique key uk_id_k(id,k)
    

    3.4 OAK Openark – kit

    openark kit 提供一组小程序,用来帮助日常的 MySQL 维护任务,可代替繁杂的手工操作。

    oak-apply-ri: apply referential integrity on two columns with parent-child relationship.
    oak-block-account: block or release MySQL users accounts, disabling them or enabling them to login.
    oak-chunk-update: Perform long, non-blocking UPDATE/DELETE operation in auto managed small chunks.
    oak-kill-slow-queries: terminate long running queries.
    oak-modify-charset: change the character set (and collation) of a textual column.
    oak-online-alter-table: Perform a non-blocking ALTER TABLE operation.
    oak-purge-master-logs: purge master logs, depending on the state of replicating slaves.
    oak-security-audit: audit accounts, passwords, privileges and other security settings.
    oak-show-limits: show AUTO_INCREMENT “free space”.
    oak-show-replication-status: show how far behind are replicating slaves on a given master.
    

    示例

     python oak-online-alter-table -u root --ask-pass -S /u01/mysql/my3306/run/mysql.sock -d replTestDB -t sbtest1 -g new_sbtest1 -a "add last_update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,add key last_update_time(last_update_time)" --sleep=300 --skip-delete-pass
    

    实现细节

    # 1.确认该表是否符合 oak-online-alter-table 的执行条件:
     已有触发器? 检查触发器->备份触发器->删除触发器
    SELECT TRIGGER_SCHEMA,TRIGGER_NAME,EVENT_OBJECT_SCHEMA,
    EVENT_OBJECT_TABLE
    FROM information_schema.TRIGGERS
    WHERE event_object_schema = 'replTestDB';
    
    Select * from information_schema.key_column_usage where
    Referenced_table_schema='replTestDB' and
    Referenced_table_name='sbtest1';
    
    #2.执行 oak 命令,至于执行时间: 
    如果表有 1亿,大概要执行 12 个小时,就需要在一周业务量少的时候执行
    
    cd /u01/tools/openark-kit-196/scripts/
    python oak-online-alter-table -u root --ask-pass -S /u01/mysql/my3306/run/mysql.sock -d replTestDB -t sbtest1 -g new_sbtest1 -a "add last_update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,add key last_update_time(last_update_time)" --sleep=300 --skip-delete-pass
    
    
    #3.Online DDL 之后要进行数据一致性校验:如果 DDL 改变了表字段类型,可能导致表数据变化
    
    
    #4.表切换
    rename table sbtest1 to old_sbtest1,new_sbtest1 to sbtest1;
    
    #5. 删除触发器
    drop trigger sbtest1_AI_oak;
    drop trigger sbtest1_AU_oak;
    drop trigger sbtest1_AD_oak;
     
    #6.删除表
  • 相关阅读:
    PAT 1088. Rational Arithmetic
    PAT 1087. All Roads Lead to Rome
    PAT 1086. Tree Traversals Again
    PAT 1085. Perfect Sequence
    PAT 1084. Broken Keyboard
    PAT 1083. List Grades
    PAT 1082. Read Number in Chinese
    求最大公因数
    [转载]Latex文件转成pdf后的字体嵌入问题的解决
    [转载]Matlab有用的小工具小技巧
  • 原文地址:https://www.cnblogs.com/chinesern/p/7677379.html
Copyright © 2011-2022 走看看