一: 事务
概念: 事务指的是满足 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, 提升查询性能以及节约系统开销
增加冗余, 提高可用性
来源: http://www.bubuko.com/infodetail-3172800.html