MySQL原理

一.主从复制原理

Mysql要做到主从复制,就是A服务把自己所做的增删改的操作全都记录在日志中,B数据库就根据这份日志上面的操作在自己身上再操作一遍,这样就实现了主从复制

  1. 当 master 主服务器上的数据发生改变时,则将其改变写入二进制日志文件(binlog)中;

  2. salve 从服务器会在一定时间间隔内对 master 主服务器上的二进制日志进行探测,探测其是否发生过改变;

  3. 如果探测到 master 主服务器的二进制日志发生了改变,则开始一个 I/O Thread 请求 master 二进制事件

  4. 同时 master 主服务器为每个 I/O Thread 启动一个dump Thread,用于向其发送二进制事件;

  5. slave 从服务器将接收到的二进制事件保存至自己本地的中继日志文件(relay log)中;

  6. salve 从服务器将启动 SQL Thread 从中继日志relay log中读取二进制日志,在本地重放,使得其数据和主服务器保持一致;

  7. 最后 I/O Thread 和 SQL Thread 将进入睡眠状态,等待下一次被唤醒; MySql高并发的处理方案就是多主多从,可以极大地提高数据库的容灾能力.

二.MyCat中间件管理Mysql集群原理

Mycat已经不是一个单纯的MySQL代理了,可以支持MySQL、SQL Server、Oracle、DB2、PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL存储,未来还会支持更多类型的存储 Mycat主要作用

  • 数据库的读写分离 (自动实现)

    当主出现故障后,mycat自动切换到另一个主上,进而提供高可用的数据库服务,当然我需要部署多主多从的模式
  • 数据库分库分表

    • Mycat的水平拆分

      拆表,一张表中的数据拆分到多个服务器上水平拆分适合数据量巨大的单表,一般的项目比较少的会使用这个,因为一张表就有数十千万的数据量不多见

    • Mycat的垂直拆分:不同的表不同库

三、事务的核心属性:ACID

  • 原子性(A):要么不执行,要么全部执行

  • 一致性(C):各个时候的状态都是一致的,是目的。

  • 隔离性(I):事务之间不能相互影响

  • 持久性(D):保证不丢失。 Redo log 保证

1.事务并发导致的问题

  • 脏读:读未提交

  • 不可重复读:同一个事务中,同一条记录的两次查询结果不一样。

  • 幻读:同一个事务中,统计的记录数不同。(因为另外一个事务insert和update)

  • 丢失更新:两个事务同时修改,其中一次的更新被覆盖。 个人认为其实这个并不算是问题,只是业务上经常遇到的问题。

2.更新丢失的解决思路

举例:两个事务都是A+100 ,可能出现只加了100的情况。

       select a  where id = 1 ;
  a1 = a + 100
  update a = a1  where id = 1 ;
  • 解决思路

    • 单条语句的原子性:update T set a = a + 50 where id = 1 ;

      • 这个案例是可以的,但是应对比较复杂的场景时,可能不可以。

    • 悲观锁

      select a  where id = 1 for update
      update T set a = a + 50 where id = 1
      commit
      • 缺点:不利于并发、且容易出现数据库死锁。

    • 乐观锁:利用CAS(compare and set)

      while(!result){
         select a,version  where id = 1;
      a1 = a + 100
         update T set a = a1 where id = 1 and version = version0;
      }
    • 分布式锁(复杂场景推荐)

      • 如果非常复杂的场景,涉及到很多表的结果,那么使用分布式锁。

    • 常见死锁的场景

      顺序相反的Update语:
      事务1  update T1 where id =1;
             update T1 where id =5;

      事务2  update T1 where id =5;
             update T1 where id =1;
    • 隔离级别

      • 读未提交:相当于什么都没做

      • 读已提交:解决了脏读问题,可能出现、不可重复读、幻读、丢失更新

        • Oracle的事务隔离级别是读已提交。

      • 可重复读:解决了脏读、不可重复读

        • 注意:Mysql的事务隔离级别是可重复读,但是它通过技术手段解决了幻读问题。

      • 串行化:事务串行执行,能保证强隔离、但是性能最低。

四、事务的实现

  • 问题:修改数据后同步刷新到磁盘性能低下。

  • 需求:为了获得好的效率,不管对于已提交的事务还是未提交的事务,可以立即刷盘,也可以延迟刷盘。

  • 解决思路:内存操作数据+ Write-Ahea Log技术实现,即提交事务时只保证Write-Ahea Log文件刷盘,内存操作数据异步刷盘。如果宕机了可以通过Write-Ahea Log恢复数据。

    • InnoDB中的实现:Redo log + Undo log

1.Master Thread 线程(刷脏页等)

  • Master Thread 每秒一次的操作

    • 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)

    • 合并插入缓冲(可能)

      • 前一秒内发生的IO次数是否小于5,那么合并插入

    • 至多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能)

      • 缓存池脏页比例 buf_get_modified_ratio_pct >innodb_max_dirty_pages_pct(默认90%)时,将100脏页写入磁盘中。

    • 如果当前没有用户活动,则切换到background loop(可能)

  • Master Thread 10秒的操作 1、刷新100个脏页到磁盘(可能的情况下) 2、合并至多5个插入缓冲(总是) 合并插入缓冲的时机是,刷脏页之后 3、将日志缓冲刷新到磁盘(总是) 4、删除无用的undo页(总是) 5、刷新100个或者10个脏页到磁盘(总是)。 缓存池脏页比例 buf_get_modified_ratio_pct有超过50%的脏页面,则刷新100个脏页到磁盘,如果脏页的比例小于70%,则只需要刷新10%的脏页面到磁盘

Master Thread 若当前没有用户活动(数据库空闲)或者数据库关闭(shutdown),就会切换到这个循环。background loop会执行以下操作: 1、删除无用的undo页(总是) 2、合并20个插入缓存(总是) 3、跳回到主循环(总是) 4、不断刷新100个页面知道服务条件(可能,跳转到flush loop中完成)

综上,InnoDB存储引擎最大只会刷新100个脏页到磁盘,合并20个插入缓冲。随着磁盘技术飞速,现在这两个值可以调整,可以适当调高。 1、在合并插入缓冲时,合并插入缓冲的数量为Innodb_io_capacity值的5% 2、在从缓冲区刷新脏页时,刷新脏页的数量为Innodb_io_capacity

(2)Redo log 持久性原理。 和Page数据一样,Redo log先写到Redo log cache内存中,再刷盘。 Redo log本身也可以是异步的,由innodb_flush_log_at_trx_commit控制刷盘策略。 0:不刷盘 1:每提交一次,刷一次盘。 2:表示每次事务提交时都只是把redo log写到文件系统page cache,由innodb_flush_log_at_timeout值决定刷盘策略。

   除此之外 Master_thread还有两种情况的刷盘:
            每秒一次
            日志缓存大于1/2时,刷一次
            每10秒一次
             
总结:至于1能保证不丢失数据,虽然慢了些。

(3)Redo log 特点:Redo log是一个固定大小的文件,循环使用。

     日志记录方法:
  a.类似Binlog 的Statemement(逻辑记法):记录SQL
      b.类似Binlog 的Row(逻辑记法),记录每张表的修改前的值和修改后的值。
      (表,行,修改前的值,修改后的值)
      c.物理日志记法(逻辑记法):记录每个Page修改前的值和修改后的值。
      (PageID,offset1,length1,修改前的值,修改后的值)
      (PageID,offset2,length2,修改前的值,修改后的值)

Redo log采用物理日志记法和逻辑日志记法综合(Physiological Logging):
  先以Page为单位记录日志,每个Page中采用逻辑记法

(4)由于不同的事务日志在Redo log 中是交叉的,未提交的事务也会在Redo log 中。

    宕机后重启,通过重新根据Redo log重放(当然,需要通过一定的算法确认从哪里开始执行,以及幂等性)。
因为Redo log也会执行包含的未提交的事务日志,所以对于未完成的日志,需要用Undo log回滚。
    ps:Undo log只在事务提交过程中有用,一旦提交了,就没用了,不能再回滚了。

(5)Undo log的事务回滚的作用是:转换成相应的事务语句执行, 比如delete 回滚 变成insert执行。
      实际执行过程和一般的事务没区别。即执行时也是顺序写LOG

综上要点:不管是已提交的事务还是未提交的事务都会写Redo log,
                      不管是已提交的事务还是未提交的事务的Page数据可能已经刷盘。
  未提交的事务对于的Page数据在宕机后会回滚。
  事务的回滚不是“物理回滚”,而是转换成SQL执行commit。

五、Undo log

(1)Undo log三大作用 宕机未提交回滚 实现ACID中的I隔离性:多版本并发控制 (Multi Version Concurrency Control MVCC ) 可重复读,从当前事务开始的时候,到事务结束,select读取的是快照。 读提交:当前语句开始的最新快照 快照由Undo log计算得到 高并发 (2)并发控制的三种读写策略 互斥锁:写写互斥、读写互斥、读读互斥 读写锁:写写互斥、读写互斥、读读并发 CopyOnWrite:读写、写写、读读都可以并发。 如java中的CopyOnWriteArrayList ps:InnoDB中的CopyOnWrite思想是使用Undo log实现的。

(3)隔离性的实现:每条记录有多版本 快照读(读的可能是历史版本):select语句 快照由Undo log计算得到 快照读读取的是每个事务开始的最新版本,所以随着时间的推移,可能被其他版本修改了, 所以可能是历史版本。

     当前读(读的是最新版本):select for update语句
                                                  insert/update/delete

(4)Undo log 和 Redo log 都是每执行一个语句,就会写入缓存中,而且保证提交后已刷盘。

六、Binlog

(1)Binlog是位于存储引擎的上层,而不在存储引擎层,主要作用是节点间的复制。 注意:Undo log 和 Redo log都是InnoDB存储引擎的日志。

(2)Binlog-format格式Statemement、Row、mixed 基于行的复制(Row模式):复制的是真实数据。 基于语句的复制(Statemement):复制的是SQL语句。 混合模式的复制(mixed):推荐的值。 正常情况下(大多数SQL)都是使用自动Statemement模式; 对于使用Statemement模式复制不安全场景中自动采用Row模式

(3)写入逻辑:事务执行过程中,先把日志写到文件系统binlog cache,事务提交的时候,再把binlog cache写到binlog文件中

  write(写cache) 和fsync(刷盘)的时机,是由参数sync_binlog控制的:

sync_binlog=0的时候,表示每次提交事务都只write,不fsync;

sync_binlog=1的时候,表示每次提交事务都会执行fsync;

sync_binlog=N(N>1)的时候,表示每次提交事务都write,但累积N个事务后才fsync。

为了不丢失数据,需要设置成1。

(5)Binlog 与 Redo log的二阶段提交,和分布式事务的两阶段2PC提交时一样的。

 第一阶段 : InnoDB prepare :持有prepare_commit_mutex,并且write/sync redo log; 将回滚段设置为Prepared状态,binlog不作任何操作 
  说明:a、是否刷盘取决于innodb_flush_log_at_trx_commit参数,设置为1时才在此时提交。
      b、为提供性能,也支持组提交的刷盘。
第二阶段:包含两步,

  1> write/sync Binlog
    说明:a、是否刷盘取决于sync_binlog参数,设置为1时才在此时提交。。
        b、为提供性能,也支持组提交的刷盘来减少刷盘次数,下面两个参数可以控制组提交。
binlog_group_commit_sync_delay=N   :延迟多少时间(等待数据一起刷盘)
binlog_group_commit_sync_no_delay_count=N :等待事务个数一起刷盘
 
  2> InnoDB commit (写入COMMIT标记后释放prepare_commit_mutex)
以 binlog 的写入与否作为事务提交成功与否的标志,innodb commit标志并不是事务成功与否的标志。
因为此时的事务崩溃恢复过程如下:
1> 崩溃恢复时,扫描最后一个Binlog文件,提取其中的xid;
2> InnoDB维持了状态为Prepare的事务链表,将这些事务的xid和Binlog中记录的xid做比较,
  如果在Binlog中存在,则提交,否则回滚事务。
  如果在写入innodb commit标志时崩溃,则恢复时,会重新对commit标志进行写入;
  在prepare阶段崩溃,则会回滚,在write/sync binlog阶段崩溃,也会回滚。

七、主从

主从形式: 一主一从 主主复制(互为主从) 一主多从(常用于扩展系统读取性能,因为读是在从库读取的) 多主一从(5.7开始支持) 联级复制

主从模式: 脑裂:主机网络连接短时间丢失,后又重新联机,出现两个主机。 处理方式:确保挂掉的服务器真的挂了,使用kill -9 杀。 主从复制的三种模式: 异步模式(默认):slave主动拉, 同步模式(Master主动push):必须所有从节点返回成功才commit。 半同步模式:主节点至少收到一台从节点返回成功才commit。 否则需要等待直到超时时间然后切换成异步模式再提交

双主模式:常用用于为了方便不同地理位置上的用户。 参数log_slave_updates设置为on,表示备库还行relay log后生成的binlog 主和主之间互为主备,当从A主机写入的数据,会在B主机中重放。 为了解决循环复制的问题:保证两个Master的service id不同。

级联复制(分担主节点复制的压力):让3~5个从节点连接主节点,其它从节点作为二级或者三级与从节点连接。

主从延迟来源: 备机性能比主机差,因为有主备切换,所有一般会购买同规格的机器。 备库压力大:使用不合理,解决的方法可以多接几个从库。 大事务:主库执行完才写binlog,所以一个10分钟的语句,到从库可能延迟10分钟。 比如一次性delete大量数据,可以改进:分多个事务删除。 备库的复制能力 网络延迟

主从延迟的读写分离方案(延迟不可避免)

 强制走主库(主流):不允许读旧数据的一律主库,允许的从库。
    存在的问题:如果大多数业务都不能有延迟,那主库压力大。
   
sleep方案:睡眠几秒再读。 听着也不靠谱

判断主备无延迟方案:
    (1)判断seconds_behind_master是否等于0
    (2)比位点方案:
        Master_Log_File和Read_Master_Lig_Pos表读到的主库最新位点
        Relay_Master_Log_File和Exec_Master_Lig_Pos表备库执行的最新位点
    (3)对比GTID是否确保主备不延迟
        但是总是延迟的,因为即使日志是最新的,但是日志文件传过来到执行也需要时间。

配合semi-sync方案:确保有一个从库返回OK了才提交事务。
    缺点:性能问题、对多从的情况下,仍然有问题。
   
等主库位点方案:查找等未点,如果超过1秒,就是转到从库去读。
    缺点:超过等待时间,主库压力

等GTID方案:查找XXX未点,如果超过时间,就是转到从库去读。
    缺点:超过等待时间,主库压力

主备切换策略: 可靠性优先: (1)seconds_behand_master小于某个值(比如5秒)继续下一步,否则重试。 (2)主库设置为只读 (3)等待seconds_behand_master值为0后,把从库改成可读写。 (4)切换到从库 说明:一般由HA系统完成;系统存在一定的不可用时间,但是保证了可靠性。

可用性优先:把从库改成可读写,然后直接切换。会丢失数据,不推荐。 一主多从切换策略:(案例中一主多从,还有主机中还有一备节点,主机故障切换到备节点)

      主机故障切换到备节点: 备机提升为新主机后,从节点需要重新指向新主机。
为了解决确定同步点的难题,MySQL5.6 引入了GTID.

八、存储结构和索引的实现

(1)索引采用B+数的逻辑结构: 主键索引采用B+树,找到的是记录的指针。 非主键索引也是一颗B+数,找到的主键的值。再用主键的值去查主键的B+树查找结果。 (2)B+树与磁盘是怎样对应的?

(3)B+树

 

上一篇:RAC中的gc current block busy与redo log flush


下一篇:一条更新语句在MySQL上是如何执行的