1、MySQL主备切换

  readonly 设置对超级(super)权限是无效的,而用于同步更新的线程,就拥有超级权限。

  建议在做主备数据库的时候,将备用数据库设置为只读。(反向用readonly来判断节点的角色)

  主备的同步是通过 binlog 日志同步,流程:

  1)、备库上通过 change master 命令,设置主库的 IP、端口、用户名、密码,以及从哪个位置开始请求 binlog,这个位置包含了文件名称和日志偏移量;

  2)、备库执行 start slave 命令,备库会启动两个线程。其中一个io_thread 负责与主库建立连接;

  3)、主库校验完用户名、密码后,开始按照备库传过来的位置,从本地读取 binlog ,发给备库;

  4)、备库拿到 binlog 后,写入本地文件(中转日志 relay log);

  5)、sql_thread 读取中转日志,解析出日志里面的命令,执行。

  binlog 的 format 除了 statement 和 row ,还有第三种格式叫做 mixed。MySQL会自己判断某条SQL语句是否可能引起主备不一致,如果可能有,就用 row 格式,否则就用 statement 格式。

  现在越来越多的场景需要将 binlog 的格式设置为 row 格式,便于之后的数据恢复。

  binlog 在记录 event 的时候,多记了一条命令:SET TIMESTAMP = xxx。约定了接下来的 now() 函数的返回时间。

  如果主备数据库是双M设置,可能会产生循环复制,就得规定两个库的 server id。用 server id 的值来判断,binlog 日志是否应该丢弃。

2、MySQL是怎么保证高可用

  主备的"同步延迟",可以在备库上执行 show slave status 命令,返回结果里的 seconds_behind_master,表示当前备库延迟了多少秒。

  主备延迟的主要来源是:备库接收完 binlog 和执行完这个事务之间的时间差。(备库消费中转日志 relay log 的速度)

  现在比较常见的部署是:对称部署,主备选用相同规格的机器。

  但是也可能出现延迟,有可能是备库的压力大。(忽视了备库的压力控制,备库上的查询耗费了大量的CPU资源,影响了同步速度,造成了主备延迟)可以用一主多从的方式解决。

  一主多从也可能导致延迟,就涉及到大事务。

  可靠性优先策略优先于可用性优先策略,MySQL高可用系统的可用性,依赖于主备延迟。当延迟的时间越小,主库故障的时候,服务恢复需要的时间就越短,可用性越高。

  扩展知识

    主备同步延迟,工作中常遇到的几种情况:

    1)、主库做大量dml的操作,引起延迟;

    2)、主库有个大事务在处理,引起延迟;

    3)、对myisam存储引起的表做dml操作,从库有延迟;

    4)、利用pt工具对主库的大表做字段新增、修改和添加索引等操作,从库会有延迟。

3、主备的并行复制策略

  主备的并行复制能力。

  主库上影响并发度的原因是各种锁,由于InnoDB支持行锁,所以业务并发的支持度还是很好的。

  备库的多线程复制机制,就是把只有一个线程的 sql_thread 拆成多个 worker 线程(从中转日志写数据到备库中,MySQL 5.6版本之前只支持单线程复制)

  worker 线程的个数是由 slave_parallel_workers 决定(32核物理机该值设置为8-16最好)。

  coordinator 分发任务给 worker 的时候,需要满足2个原则(各个MySQL版本的多线程复制):

  1)、不能造成更新覆盖,更新同一行的两个事务必须分发到同一个 worker 中。

  2)、同一个事务不能被拆开,必须放到同一个 worker 中。

  按表分发策略:每个事务可能和多个 worker 产生冲突关系(一个事务修改多个表,且多个表在多个 worker 中已经等待),就会等待到只和一个 worker 产生冲突,然后分配给该 worker。如果碰到热点表,大部分的更新事务都会涉及到一个表,所有事务都会被分配到同一个 worker 中,就变成单线程复制了。

  按行分发策略:如果两个事务没有更新相同的行,它们在备库上可以并行执行。(这个模式要求 binlog 的格式必须是 row ,步骤之类的和按表分发策略相似。

  MySQL 5.6 的并行复制策略:按库并行,这个策略的并行效果,取决于压力模型。在主库上有多个DB,并且各个 DB 的压力均衡,使用这个策略的效果会很好。(不要求 binlog 的格式)这个策略用的不多。

  MariaDB 的并行复制策略:在同一组里提交的事务,一定不会修改同一行(一组的事务有相同的commit_id ,相同的 commit_id 分发给不同的 worker 执行)。主库上可以并行执行的事务,备库上也一定可以并行执行的。(很容易被大事务拖后腿)

  MySQL 5.7 并行复制策略:同时处于 prepare 状态的事务,在备库执行时是可以并行的(经过了锁冲突的检验)。处于 prepare 状态的事务,与处于 commit 状态的事务之间,在备库执行时也是可以并行的。

4、一主多从下的主备切换

  在一主多从架构下,主库故障后的主备切换问题。(一主一备互为主备,还有多从)

  当主库故障后,备库就会成为新主库,其他从库重新指向备库(新主库)。

  基于位点的主备切换:当备库切换为主库,其他从库指向新主库的日志位置和偏移量,就随之改变。去同步位点法得到位置 T,但是通常会遇到主键冲突或者删除行不存在的错误(因为日志位置不精确),可以主动跳过事务,用 set global sql_slave_skip_counter=1; start slave; 命令跳过,或者设置 slave_skip_errors 参数为 1062(插入主键冲突)、1032(删除行不存在)。

  但是这种方法很复杂并且很容易出错。

  GTID(Global Transaction Identifier):生成一个唯一的全局事务ID,并且是连续递增的。格式为 GTID=server_uuid:gno ,server_uuid 为实例第一次启动时生成,gno 为连续递增整数(事务提交的时候分配的)。启动的方式是在启动一个 MySQL 实例的时候,加上参数 gtid_mode=on 和 enforce_gtid_consistency= on。

  在 GTID 模式下,每个事务都会跟一个 GTID 一一对应。每个 MySQL 实例都会维护一个 GTID 集合,用来对应"这个实例执行过的所有事务"。

  基于 GTID 的主备切换:master_auto_position = 1 表示这个主备关系使用的是 GTID 协议。备库实例的 GTID 集合记为 set_a ,从库实例的 GTID 集合记为 set_b。

  1)、从库指向备库,基于主备关系建立连接;

  2)、从库把 set_b 发给备库;

  3)、备库算出 set_a 和 set_b 的差集,也就是存在于 set_a,不存在 set_b的GTID 的集合,判断备库是否包含了这个差集需要的所有 binlog 事务。不包含,表示备库已经把从库需要的 binlog 给删掉了,直接返回错误。 包含,备库从自己的 binlog 文件里面,找出第一个不在 set_b 的事务,发给从库。

  4)、之后从这个事务开始,往后读文件,按顺序取 binlog 发给从库去执行。

5、读写分离

  1)、客户端直连方案,客户端选择后端数据库进行查询。查询性能稍微好些,但是需要了解后端的部署细节,出现主备切换、库迁移等操作,就要相应的调整数据库信息。

  2)、带 proxy 的架构,客户端只需要连接 proxy,对后端维护团队的要求比较高,并且需要 proxy 有高可用架构。主要还是选 proxy 这种架构方向。

  如果主从存在延迟,客户端马上更新完主库数据,主从延迟,从库查询,查到的刚刚更新事物之前的状态,可以叫做"过期读"。

  处理过期读:

  1)、强制走主库方案:对于必须拿到最新结果的请求,强制将该请求发送到主库上。对于可以拿到旧数据的请求,才将其发到从库上。

  2)、Sleep 方案:主库更新后,读从库之前先 sleep 一下(类似执行一条 select sleep(1) 命令),这样很大概率会在从库拿到最新数据。

  3)、判断主备无延迟方案:从库执行查询请求前,先判断 seconds_behind_master 是否等于0,等于0的时候才能执行查询请求。还有对比位点来判断,接收到的日志是否已经同步完成。最后可以对比GTID集合,备库收到的所有日志的GTID集合和备库所有已经执行完的GTID集合相同,则主从无延迟。

  4)、配合 semi-sync(半同步复制):事务提交的时候,主库把 binlog 发给从库,从库收到 binlog 以后,发回给主库一个 ack,表示收到了,主库收到这个 ack 以后,才能给客户端返回 "事务完成"的确认。启用了 semi-sync,表示所有给客户端发送过确认的事务,都确保了备库已经收到了这个日志。但是该方法只对 一主一从 的场景成立。

  5)、等主库位点方案:

  6)、GTID方案:

6、如何判断一个数据库出问题了

  select 1 判断

  其实 select 1 成功返回,只能说明这个库的进程还在,并不能说明主库没问题。例如一个数据库连接数达到最大值,再接受到新的请求,就会进入等待状态。此时 select 1 是成功的,但是编写的 sql 语句肯定会阻塞。将 innodb_thread_concurrency 设置为 64-128之间,这是并发查询参数。

  并发连接:show processlist 看到的几千个连接就是并发连接。

  并发查询(并发线程查询):当前正在执行的 sql 语句。并发查询太高才是 CPU 杀手。在线程进入锁等待以后,并发线程的计数会减一。(行锁和间隙锁的线程是不算在 128 参数值里面的,进入锁等待的线程已经不吃 CPU 了,这样设计,避免整个系统锁死)

  查表判断

  新建一个表,只放一行数据,定期查询。但是空间满了以后这方法又不好使。更新事务要写 binlog,而一旦 binlog 所在的磁盘的空间占用率达到了 100%,那么更新语句和事务提交的 commit 语句就会被堵住。 但是,系统这时候还可以正常读数据。所以就检测不出来是否出问题了。

  更新判断

  新建一个表,更新时间字段,查看返回结果。但是更新得更新主备库。在主库和备库执行相同的更新命令,肯能出现冲突,导致主备同步停止。 所以表中得多存数据,并且用主备库的 server_id 做主键。

  内部统计

  performance_schema 库的 file_summary_by_event_name 表里统计了每次 IO 请求的时间。

  其实 select 1 是默认使用的判断数据库的方法。因为要从性能和业务实际情况做权衡。

7、误删数据的恢复办法

  传统的高可用框架是不能预防误删数据的,主库的一个 drop table 命令,会通过 binlog 传给所有的从库和级联从库,从而导致整个集群的实例都会执行这个命令。

  误删行

  可以通过 Flashback 工具通过闪回把数据恢复过来。原理是修改 binlog 的内容,拿回原库重放。

  前提是 binlog_format=row 和 binlog_row_image=FULL。

  恢复举个例子:对于错误的 delete 语句,将 binlog_row 类型是 Delete_rows event 改为 Write_rows event 这种反向恢复。 对于 update 语句,binlog 的 row 记录修改前后的值,对调这两个值的位置就可以。

  如果误操作是多个 sql 语句的事务,恢复的时候就将事务的顺序颠倒再执行。

  不建议在主库上直接执行这些操作,恢复数据比较安全是,恢复出一个备份,找一个从库做临时库,先恢复临时库,确认数据后,再恢复主库。

  事前预防:

  1)、设置 sql_safe_updates=0。如果忘了写 where 条件,或者 where 条件里面没有包含索引字段,这条语句的执行就会报错。如果真要删除整个表,那就加 where id >=0 这种条件。

  2)、代码上线前,必须经过 SQL 审计。

  删除表,优先考虑 truncate table 或者 drop table 命令。

  误删库/表

  如果想恢复误删库/表的数据,就需要使用全量备份,加增量日志的方式。要求线上有定期的全量备份,并且实时备份 binlog。

  流程:

    1)、取出最近的一次全量备份,假如是凌晨0点备份的;

    2)、用备份恢复出一个临时库;

    3)、从日志备份里面,取出0点之后的日志;

    4)、把除了误删除语句的这些日志,全部应用到临时库中。

  第四步中,如果实例使用了 GTID 模式,假如误操作的 GTID=gtid1,那么只需执行 set gtid_next=gtid1;begin;commit; 先把这个 GTID 加到临时实例的 GTID 集合,之后按顺序执行 binlog 的时候,就会自动跳过误操作的语句。

  注意:mysqlbinlog 工具并不能指定只解析一个表的日志,会恢复整个库的所有表。 mysqlbinlog 解析出日志应用,应用日志的过程就只能是单线程。

  加速办法:在用备份恢复出临时实例之后,将这个临时实例设置成线上备份库的从库,在 start slave 之前,先执行 change replication filter replicate_do_table=(tbl_name)命令,可以让临时库只同步误操作的那一个表。这样做也可以用上并行复制技术,加速整个数据恢复过程。

  延迟复制备库

  通过 change master to master_delay=N 命令,可以指定该备库持续保持跟主库有N秒的延迟。

  预防误删库/表的方法

  1)、账号分离,给业务开发的同学开发库的 DML 权限,不给 truncate/drop 权限。DBA 团队成员日常也用只读账号。

  2)、指定操作规范,删除数据表之前,先对表做改名 rename 操作。 观察一段时间确实对业务无影响再删除这张表。 改名最好加固定的后缀格式,删除的时候就不会出错。

  扩展知识

    修改生产数据库的数据,或者添加索引优化,要写好四个脚本:备份脚本、执行脚本、

    验证脚本和回滚脚本。这样4个脚本能很大程度上避免数据误操作。

    最主要的思维:备份

    MySQL 备份的有效性的验证,可以通过不定期的备份恢复来验证。

8、有哪些 kill 不掉的命令

  MySQL有两个 kill 命令:一个 kill query + 线程id ,表示终止这个线程中正在执行的语句;

  一个 kill connection + 线程 id ,表示断开这个线程的连接,如果这个线程有语句在执行,就先停止语句再停止连接。

  收到 kill 时,线程做的事情

  kill 并不是马上停止的意思,而是告诉线程,这句 sql 不需要执行了,可以开始 "执行停止的逻辑了"。当用户执行 kill query thread_id 时,MySQL里处理 kill 命令的线程做了两件事:

  将 session 的运行状态修改为 THD:KILL_QUERY;然后给 session 的执行线程发一个信号(如果只是把 session 的线程状态设置为 THD:KILL_QUERY,线程并不知道这个状态变化,还是会继续等待锁释放之类的行为)。

  sql 语句执行过程中有多处 "埋点",在这些"埋点"的地方判断线程状态,如果发现线程状态是 THD:KILL_QUERY,才开始进入语句终止逻辑,并且到终止逻辑完全完成,是有一个过程。

  如果是 kill connection ,本质上是把客户端的 sql 连接断开,后面的执行流程还是要走 kill query流程。当 show processlist 展示连接列表时, kill conncetion 会显示 "killed"。

  例如一个 kill 不掉的例子

  innodb_thread_concurrency 不够的情况,线程被堵住了,执行 kill query session 就没什么效果,得执行 kill connection 命令,才断开 session的连接,此时该线程如果 show processlist 的话,该线程的 Commnad 列显示的就是 Killed。(等待锁的等待状态要被唤醒)

  上面的 kill connection 命令是把线程状态设置为 KILL_CONNECTIOON。如果一个线程的状态是 KILL_CONNECTION,就把 Command 列显示为 killed。

  所以即使是客户端退出了,这个线程的状态仍然是在等待中。然后等到满足进入InnoDB的条件后,语句继续执行,判断线程的状态变成 KILL_QUERY或者KILL_CONNECTION,再进入终止逻辑阶段。

  两种情况,一种就是上面的:线程还没有执行到判断线程状态的逻辑。另一种情况就是,终止逻辑耗时较长。

  终止逻辑耗时较长常见情况:

  1)、超大事务执行期间被 kill;

  2)、大查询回滚;

  3)、DDL命令执行到最后阶段,被 kill, 需要删除中间过程的临时文件,也可能受 IO 资源影响耗时较久。

  库里的表很多,连接很慢的原因

  首先并不是表的数目影响了连接性能。当使用默认参数连接的时候,MySQL 客户端会提供一个本地库名和表名补全的功能。客户端在连接成功后,需要多做一些操作:

  执行 show databases;切到db库,执行 show tables;把这两个命令的结果用于构建一个本地的哈希表。尤其第三步耗时很长。所以表多,耗时就会变长。

  所以其实是客户端慢。

  在连接命令上加上 -A,就可以关掉这个自动补全的功能,客户端就可以快速返回了。

全局扩展

  根据 explain sql 语句,脑海中脑补 sql 语句的执行流程:

  1、连接MySQL;

  2、根据 where 条件选择索引(二级索引和主键索引,或者覆盖索引,或者联合索引);

  3、根据加锁规则,判断 sql 语句是否处于锁等待,还是直接执行;

  4、根据索引,查到主键 id,回表(走二级索引树定位,如果是主键索引不用回表)。或者联合索引(多个条件,只需在一个索引树上就定位到范围内的数据),或者覆盖索引(不用回表,查询的所有字段值也在索引里);

  5、根据查询的字段已经分配了一个 sort_buffer,根据符合条件的数据填充 sort_buffer,然后排序(也可以做到用主键 id 排序,然后再回表去拿所有查询字段的值,一般很少用);

  6、根据 desc 或者 asc ,和 limit 分页,取出结果集返回给前端。

  其中增删改是会写 redo_log 日志和 binlog 日志(二阶段提交。顺序写,减少随机IO。涉及到MySQL的高可用)。

  如果删除大量表数据但是表文件大小不变,就是索引树上有很多位置是空洞或者数据页利用率很低,就要重建普通索引,使用命令 alter table t engine=InnoDB。

  如果查询很慢,可能是前面的线程在堵塞。MySQL 的 InnoDB 引擎默认事务级别为 RR。

  普通索引存在 change buffer,存的是 sql 修改的操作。数据以数据页的方式和磁盘做交互。change buffer 节省的是随机读磁盘的 IO 消耗(修改等操作缓存在 change buffer 中,当查询的时候,从内存拿到数据页,就不会从磁盘直接读,内存拿不到,就从磁盘拿,并且将 change buffer 的操作应用生成新的数据页,返回和刷脏页到磁盘)

  InnoDB 显示加锁:sql 语句后加 lock in share mode 或者 sql 语句后加 for update。

该文章只是笔记,记录的是我自己大概的总结。

如果想看完整的知识点,可以去 极客时间app 上找 MySQL实战45讲 --- 林晓斌 老师的课程。

链接:https://time.geekbang.org/column/139

MySQL学习(三)主备分库分表和恢复数据 --- 2019年2月的更多相关文章

  1. Mysql系列五:数据库分库分表中间件mycat的安装和mycat配置详解

    一.mycat的安装 环境准备:准备一台虚拟机192.168.152.128 1. 下载mycat cd /softwarewget http:-linux.tar.gz 2. 解压mycat tar ...

  2. mysql主从读写分离,分库分表

    1.分表 当项目上线后,数据将会几何级的增长,当数据很多的时候,读取性能将会下降,更新表数据的时候也需要更新索引,所以我们需要分表,当数据量再大的时候就需要分库了. a.水平拆分:数据分成多个表 b. ...

  3. mysql(5):主从复制和分库分表

    主从复制集群 概念:主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点. 使用场景: 读写分离:使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读 ...

  4. MySQL全面瓦解28:分库分表

    1 为什么要分库分表 物理服务机的CPU.内存.存储设备.连接数等资源有限,某个时段大量连接同时执行操作,会导致数据库在处理上遇到性能瓶颈.为了解决这个问题,行业先驱门充分发扬了分而治之的思想,对大库 ...

  5. Mysql系列四:数据库分库分表基础理论

    一.数据处理分类 1. 海量数据处理,按照使用场景主要分为两种类型: 联机事务处理(OLTP) 面向交易的处理系统,其基本特征是原始数据可以立即传送到计算机中心进行处理,并在很短的时间内给出处理结果. ...

  6. (转) MySQL分区与传统的分库分表

    传统的分库分表 原文:http://blog.csdn.net/kobejayandy/article/details/54799579 传统的分库分表都是通过应用层逻辑实现的,对于数据库层面来说,都 ...

  7. MySQL数据库之互联网常用分库分表方案

    一.数据库瓶颈 不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值.在业务Service来看就是,可用数据库连接少甚至无连接可用.接下来就 ...

  8. MySQL学习(四)Join 等开发常用的操作 --- 2019年2月

    1.查数据太多不会把内存用光 InnoDB 的数据是保存在主键索引上,然后索引树分割保存在数据页上,数据页存在内存中/磁盘.change buffer 就是先把修改操作记录,然后读数据的时候,内存没有 ...

  9. 三十八. 分库分表概述 配置mycat

    1.搭建mycat 分片服务器   数据库主机 192.168.4.55 使用db1库存储数据 数据库主机 192.168.4.56 使用db2库存储数据 主机 192.168.4.54 运行myca ...

随机推荐

  1. .NET Core 获取操作系统各种信息

    .NET Core 获取操作系统各种信息 一.前言 .NET Core 内置了一些API供我们获取操作系统.运行时.框架等信息.这些API不是很常用,所有有些小伙伴可能还不知道,这里做一些可能用到的获 ...

  2. 在centos上面安装phantomjs

    在opt目录下创建phantomjs文件夹 mkdir -p /opt/phantomjs 把phantomjs解压出来的的文件放到/opt/phantomjs下面 建立软链接 ln -s /opt/ ...

  3. Postman 使用详解

    转自: http://blog.csdn.net/flowerspring/article/details/52774399 Postman 使用详解 阅读 6754收藏 3022016-6-9 作者 ...

  4. 理解、学习与使用 Java 中的 Optional

    从 Java 8 引入的一个很有趣的特性是 Optional  类.Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) -- 每个 Java 程序员都 ...

  5. engine_init_options.go

    package ) type {         options.PersistentStorageShards = defaultPersistentStorageShards     } }

  6. golang 并发模式笔记

    1.并发并不是并行,前者是优先对时间片的抢占,后者是真多核. go中多线程时直接要求并行的方法是: 亦不可滥用,CPU密集型,并发度很高的场景适用. 2.go起的协程 3. function that ...

  7. ||与&&的返回值

    当你准备携带你的配剑杀向江湖的时候,当你准备进入js这门语言的时候,你会遇到很多||与&&的问题.那么对于他们的返回值你知道多少呢? 在此之前我们来聊一个大家都知道的知识:js中值转换 ...

  8. C# 利用VS自带的WSDL工具生成WebService服务类

    C# 利用VS自带的WSDL工具生成WebService服务类   WebService有两种使用方式,一种是直接通过添加服务引用,另一种则是通过WSDL生成. 添加服务引用大家基本都用过,这里就不讲 ...

  9. 如何解析C语言的声明

    一个声明:int *p[] 分为四部分: (1)p (2)p右面的符号(可以什么都没有) (3)p左面的符号(可以什么都没有) (4)最左面的类型说明符 解读一个声明先从p开始,然后的顺序是:右左右左 ...

  10. 读书笔记:深入理解java虚拟机(二)创建对象的时候需要访问哪几块内存

    @TOC 对象在内存中如何储存 对象访问在java语言中无处不在,是最普通的程序行为,但即使是最简单的访问,也会涉及到java栈,java堆,方法去三个最重要的内存区域的关联关系,比如下面这段代码: ...