面试 -> MySQL

  • 一:事务

    • 概念:事务指的是满足ACID特性的一组操作,可以通过commit提交一个事务,也可以使用rollback进行回滚
      • 原子性(Atomicity)
        • 事务被视为不可分割的最小单元,事务的操作要么全部提交成功,要么全部失败回滚
        • 回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可
      • 一致性(Consistency)
        • 数据库在事务执行前后都保持一致性状态,在一致性状态下,所有事务对一个数据的读取结果都是相同的
      • 隔离性(Isolation)
        • 一个事务所做的修改在最终提交前,对其它事务是不可见的
      • 持久性(Durability)
        • 一旦事务提交,则其所做的修改将会永远保存到数据库中,即使系统宕机,事务执行的结果也不能丢失,使用重做日志来保证持久性
    • 事务的ACID特性概念简单,但是不好理解,主要是因为这几个特性不是一种平级关系MySQL默认采用自动提交模式(AUTOCOMMIT),也就是说,如果不显示使用 START TRANSACTION 语句来开始一个事务,那么每个查询都会被当做一个事务自动提交
      • 1、只有满足一致性,事务的执行结果才是正确的
      • 2、在无并发的情况下,事务串行执行,隔离性一定能够满足,此时只要能满足原子性,就一定能满足一致性
      • 3、在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性
      • 4、事务满足持久化是为了能应对数据库宕机的情况
  • 二:并发一致性问题

    • 在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性的问题
      • 丢失数据
        • T1和T2两个事务同时对一个数据进行修改,T1先修改,T2随后修改,T2的修改覆盖了T1的修改
      •  读脏数据
        • T1修改一个数据,T2随后读取这个数据,如果T1撤销了这次修改,那么T2读取的数据就是脏数据
      • 不可重复读
        • T2读取一个数据,T1对该数据做了修改,如果T2读取这个数据,此时读取的结果和第一次读取的结果不同
      • 幻读
        • T1读取某个范围的数据,T2在这个范围内插入新的数据,T1再次读取这个范围内的数据,此时读取的结果和第一次读取的结果不同
    • 产生并发不一致性问题的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户控制,非常复杂。数据库管理系统提供了事务的隔离级别,让用户更便捷的处理并发一致性问题。
  • 三:锁

    • MySQL提供了两种锁的颗粒度:行级锁,表级锁
    • 应该尽量只锁定需要修改的那部分数据,而不是所有的资源,锁定的数据量越小,发生锁争用的几率就越小,系统并发程度自然就越高
    • 但是加锁需要消耗资源,锁的各种操作(获取锁,释放锁,以及检查所状态)都会增加系统开销,因此锁的颗粒度越小,系统的开销就越大,所以在选择锁的颗粒度时,需要在锁开销和并发程度之间做一个权衡
    • 锁类型
      • 排它锁(Exclusive),简写为X锁,又称写锁,共享锁(Shared),简写为S锁,又称读锁
        • 有下面2个规定
          • 一个事务对数据对象A加了X锁,就可以对A进行读取和更新,加锁期间其它事务不能对A加任何锁
          • 一个事务对数据对象A加了S锁,可以对A进行读取操作,但是不能进行更新操作,加锁期间其它事务能对A加S锁,但是不能加X锁
        • 锁的兼容关系
          • 如下
      • 意向锁(Intention Locks)
        • 使用意向锁可以更容易地支持多颗粒度
        • 在存在行级锁和表级锁的情况下,事务T想要对表A加X锁,就需要先检测是否与其它事务对表A或者表A中的任意一行加了锁,也就是说需要对表A的每一行都检测一次,这是非常耗时的操作
        • 意向锁在原来的X/S锁之上引入了IX/IS,,IX/IS都是表锁,用来表示一个事务想要在表中的某个数据行上加X锁或者S锁,有以下2个规定
          • 一个事务在获得某个数据行对象的S锁之前,必须先获得表的IS锁或者更强的锁
          • 一个事务在获得某个数据行对象的X锁之前,必须先获得表的IX锁
        • 通过引入意向锁,事务T想要对表A加X锁,只需要先检测是否有其它事务对表A加了X/IX/S/IS锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务T加X锁失败
        • 各种锁兼容关系
          • 如下
        • 解释:
          • 任意IS/IX锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正的加锁
          • S锁只与IS锁兼容,也就是说事务T想要对数据行加S锁,其它事务可以获取已经对表或者表中行的S锁
    • 锁协议
      • 一级锁协议
        • 事务T要修改数据A时必须加X锁,直到T结束才释放锁
        • 可以解决丢失修改问题,因为不能有两个事务同时对一个数据进行修改,那么事务的修改就不会被覆盖
        • 如下
      • 二级锁协议
        • 在一级的基础上,要求读取数据A时必须要加S锁,读取完马上释放S锁
        • 可以解决读脏数据问题,因为如果一个事务在堆数据A进行修改,根据一级锁协议,会加X锁,那么就不能再加S锁了,也就是不会读入数据
        • 如下
      • 三级锁协议
        • 在二级的基础上,要求读取数据A时必须要加S锁,直到事务结束了才能释放S锁
        • 可以解决不重复读问题,因为读A时,其它事务不能对A加X锁,从而避免了在读的期间数据发生改变
        • 如下
      • 两段锁协议
        • 加锁和解锁分为两个阶段进行
        • 可串行化调度是指,通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。事务遵循两段锁协议是保证可串行化调度的充分条件,例如以下操作满足两段锁协议,它可串行化调度
          • lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)
        • 但不是必要条件,例如以下操作不满足两段锁协议,但是它也可以串行化调度
          • lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)
    • MySQL 隐式与显示锁定
      • MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定
      • InnoDB 也可以使用特定的语句进行显示锁定
        • SELECT ... LOCK In SHARE MODE;
        • SELECT ... FOR UPDATE;
  • 四:隔离级别

    • 未提交读(READ UNCOMMITTED)
      • 事务中的修改,即使没有提交,对其它事务也是可见的。
    • 提交读(READ COMMITTED)
      • 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的
    • 可重复读(REPEATABLE READ)
      • 保证在同一个事务中多次读取同样数据的结果是一样的
    • 可串行化(SERIALIZABLE)
      • 强制事务串行执行
      • 需要加锁实现,而其它隔离级别通常不需要
    • 四者兼容如下
  • 五、多并发并发控制

    • 多版本并发控制,是MySQL的InnoDB存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别,而未提交读隔离级别总是读取最新的数据行,无需MVCC,可串行化隔离级别需要对所有读取的行都加锁,单纯使用MVCC无法实现
    • 版本号
      • 系统版本号:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增
      • 事务版本号:事务开始时的系统版本号
    • 隐藏的列
      • MVCC 在每行记录后面都保存着两个隐藏的列,用来存储两个版本号
        • 创建版本号:指示创建一个数据行的快照时的系统版本号
        • 删除版本号:如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则表示该快照已经被删除了
    • Undo 日志
      • MVCC 使用到的快照存储在 Undo 日志中,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来
    • 实现过程
      • 以下实现过程针对可重复读隔离级别
      • 当开始一个事务时,该事务的版本号肯定大于当前所有数据行快照的创建版本号,理解这一点很关键。数据行快照的创建版本号是创建数据行快照时的系统版本号,系统版本号随着创建事务而递增,因此新创建一个事务时,这个事务的系统版本号比之前的系统版本号都大,也就是比所有数据行快照的创建版本号都大
  • 六:存储引擎

    • InnoDB
      • InnoDB是 MySQL 默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其它存储引擎
      • 实现了四个标准的隔离级别,默认级别是可重复读(REPEATABLE READ)。在可重复读隔离级别下,通过多版本并发控制(MVCC)+ 间隙锁(Next-Key Locking)防止幻影读
      • 主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升
      • 内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等
      • 支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取
    • MyISAM
      • MyISAM设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它
      • 提供了大量的特性,包括压缩表、空间数据索引等
      • 不支持事务
      • 不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)
      • 可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的
      • 如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作
    • 比较
      • 事务:InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句
      • 并发:MyISAM 只支持表级锁,而 InnoDB 还支持行级锁
      • 外键:InnoDB 支持外键
      • 备份:InnoDB 支持在线热备份
      • 崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢
      • 其它特性:MyISAM 支持压缩表和空间数据索引
  • 七:主从复制

    • 主要涉及三个线程:binlog 线程、I/O 线程和 SQL 线程
    • binlog 线程 :负责将主服务器上的数据更改写入二进制日志(Binary log)中
    • I/O 线程 :负责从主服务器上读取二进制日志,并写入从服务器的中继日志(Relay log)
    • SQL 线程 :负责读取中继日志,解析出主服务器已经执行的数据更改并在从服务器中重放(Replay)
  • 八:读写分离

    • 主服务器处理写操作以及实时性要求比较高的读操作,而从服务器处理读操作
    • 读写分离能提高性能的原因在于读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器
      • 主从服务器负责各自的读和写,极大程度缓解了锁的争用
      • 从服务器可以使用 MyISAM,提升查询性能以及节约系统开销
      • 增加冗余,提高可用性
原文地址:https://www.cnblogs.com/idoljames/p/11420908.html