1. MySQL逻辑架构
1.1 连接管理与安全性
每个客户端连接都会在服务器进程中拥有一个线程, 这个连接的查询只会在这个单独的线程中执行, MySQL服务维护一个线程池
1.2 优化与执行
MySQL会解析查询SQL, 并创建解析数, 然后对SQL进行如下各种优化
- 重写查询
- 决定表的读写顺序
- 选择合适的索引
对于SELECT语句, 在解析查询之前, 服务器会先检查查询缓存
1.3 存储引擎
优化器并不关心表使用的是什么存储引擎, 单存储引擎对优化查询是有影响的, 优化器会想存储引擎发起请求, 存储引擎会提供容量或者某个操作的开销信息, 以及表数据的统计信息
2. 并发控制
多个查询同一时刻修改数据, 都会产生并发控制问题, 可以在服务器层和存储引擎层进行并发控制
2.1 读写锁
- 读锁(共享锁), 多个连接可以同时读取同一条记录
- 写锁(排它锁), 写锁会堵塞其他的读锁和写锁
2.2 锁粒度
任何时候, 在给定的资源上, 锁定的数据量越少, 则系统的并发程度越高, 只要相互不发生冲突即可, 问题是枷锁也需要消耗资源, 锁的各种操作, 包括获取锁, 检查锁是否已经解除, 释放锁等操作都会增加系统开销
所谓的锁策略, 就是在锁的开销和数据的安全性之间寻求平衡, MySQL提供了多种选择, 每种MySQL存储引擎都可以实现自己的锁策略和锁粒度
- 表锁, 最基本的所策略, 开销最小的策略
- 行级锁, 最大程度的支持并发处理, 同时也带来了最大的锁开销
3. 事务
事务是一组原子性的SQL查询, 会增加MySQL服务器的开销, 事务的ACID特性:
- 原子性(atomicity)
- 一个事务是一个不可分割的最小工作单元, 要么全部提交成功, 要么全部失败回滚, 不可能只执行其中的一部分
- 一致性(consistency)
- 数据库完整性约束的一致性, 也就是数据符合设置的约束(例如:唯一约束,外键约束等)
- 业务一致性(例如:转账前后,金额总数一致,), 由开发人员保证
- 隔离性(isolation)
- 事务在提交之前, 对于其他事务是不可见的
- 持久性(durability)
- 事务提交之后, 所做的修改就会永久保存到数据库中
3.1 隔离级别
SQL标准中定义了四种隔离级别, 规定了一个事务所做的修改, 哪些在事务内和事务间是可见的, 哪些是不可见的, 较低级别的隔离通常可以执行更高的并发, 系统开销也更低
- READ UNCOMMITTED(读未提交)
- 最低级别事务
- 当前事务没有提交, 对其他事务也是可见的
- 事务可以读取到未提交的数据, 被称为脏读
- READ COMMITED(读已提交)
- 避免了脏读
- 事务从提交开始, 所做的修改对于其他事务都是不可见的
- 会导致不可重复读问题, 同一个事务执行两次相同的查询, 可能得到不一样的结果
- REPEATABLE READ(可重复读)
- MySQL默认事务隔离级别
- 避免了脏读和不可重复读
- 保证了同一个事务中, 多次去读同样的记录, 结果是一致的
- 会导致幻读问题, 某个事务读取多条记录时, 另外一个事务插入了新的记录, 可能会产生幻读行
- SERIALIZABLE(串行化)
- 最高的隔离级别
- 强制事务串行执行
- 可能会导致大量的超时和锁征用问题, 读操作也会被加锁
3.2 死锁
死锁是指两个或者多个事务占用同一资源, 并锁定对方占用资源, 可能会导致死锁
事务1:
start transaction ;
update s_user set name = 'zhangsan' where id = 1;
update s_user set name = 'lisi' where id = 2;
commit;
事务2:
start transaction ;
update s_user set name = 'lisi' where id = 2;
update s_user set name = 'zhangsan' where id = 1;
commit;
上面两个事务, 在高并发情况下, 都先执行了第一条UPDATE, 更新了一行数据, 同时也锁定了该行数据, 然后每个事务都尝试执行第二条UPDATE语句, 却发现被对方锁定了, 导致了死锁
为了解决死锁问题, 数据库系统实现了各种死锁检测和死锁超时机制, 例如:InnoDB存储引擎, 可以检测到死锁循环依赖, 并立即返回一个错误
锁的行为和顺序和存储引擎相关, 同样的顺序执行SQL语句, 有些存储引擎会产生死锁, 有些则不会
死锁发生以后, 只有部分或者完全回滚其中一个事务, 才能打破死锁
3.3 事务日志
事务日志可以帮助提高事务的效率, 使用了事务日志之后
- 存储引擎在修改表的数据时, 只需要修改内存中的数据, 然后事务日志持久化到磁盘上的事务日志中
- 事务日志持久化之后, 内存中修改的数据会慢慢修改到硬盘中
- 如果事务日志已经持久化, 但数据没有被写回磁盘, 此时系统崩溃, 那么存储引擎在重启之后, 能够自动恢复这部分修改的数据
3.4 MySQL中的事务
MySQL提供了两种事务型存储引擎
- InnoDB
- NDB Cluster
还有一些第三方存储引擎也支持事务
- XtraDB
- PBXT
自动提交(AUTOCOMMIT)
MySQL默认此启用自动提交模式, 如果不显示的开始一个事务, 那么每个查询都被当成一个事务执行提交操作, 可以通过设置autocommmit变量来启用或者禁用自动提交模式
set autocommit = 1;
如上: 1或者ON表示启用, 0或者OFF表示禁用
当autocommit=0时, 所有查询都在一个事务中, 直到显示执行commit或者rollback回滚, 然后又开始一个新的事务
autocommit对于MyISAM引擎或者内存表, 不会有任何影响, 对于这类表来说, 没有COMMIT或者ROLLBACK的概念, 也就说一直处于autocommit启用的状态
还有一些命令, 在执行之前会强制执行commit提交当前的活动事务, 例如: 执行DDL(数据库定义语言), ALERT TABLE等导致大量数据修改的操作, 另外还有LOCK TABLES等其他语句与会导致同样的结果
set session transaction isolation level read committed;
通过执行set session transaction isolation leve 命令设置隔离级别, 隔离级别在下一个事务开始的时候生效, 也可以在配置文件中, 设置整个数据库的隔离级别
在事务中混合使用存储引擎
MySQL事务是由下层的存储引擎实现的, 所以在同一个十五中使用多种存储引擎是不可靠的
隐式和显示锁定
InnoDB采用的两阶段锁定协议, 在事务执行的过程中, 随时可以执行锁定, 锁只有在执行COMMIT或者ROLLBACK的时候才会释放, 并且所有的锁是在同一时刻被释放
- InnoDB隐式和显示锁定
- 隐式锁定: 一般的锁定都是隐式锁定, InnoDB会根据隔离级别在需要的时候自动锁定
- 显示锁定:这些语句不属于SQL规范
- SELECT ... LOCK IN SHARE MODE
- SELECT ... FOR UPDATE
- MySQL LOCK和UNLOCK语句
- LOCK和UNLOCK语句由服务器层实现, 和存储引擎无关, 它们有自己的用途, 不能代替事务处理, 如果需要使用事务, 还是应该选择事务型存储引擎
- 如果使用了InnoDB存储引擎, 还显示使用LOCK TABLES语句, 不但没有必要, 还会严重影响性能, 实际上使用InnoDB的行级锁更好
- LOCK TABLES和事务间相互影响的话, 在某些MySQL版本中甚至会出现无法预料的结果
4. 多版本并发控制
MySQL大多数事务型存储引擎实现都不是简单的行级锁, 基于提升并发性能的考虑, 一般都实现了并发版本控制(MVCC)
MVCC是行级锁的一个变种, 它在很多情况下避免了加锁操作, 因此开销更低, 虽然实现机制有所不同, 但大都实现了非阻塞的读操作, 写操作也只锁定必要的行
InnoDB存储引擎的MVCC实现:
- MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作, READ UNCOMMITTED和SERIALIZABLE和MVCC不兼容
- READ UNCOMMITTED总是读取最新的数据行
- SERIALIZABLE会对所有读取的行都加锁
- 每行记录后面保存三个隐藏列
- 6个字节的事务标识DB_TRX_ID, 插入或更新行的最后一个事务的ID, 删除视为更新, 将其标记为已删除
- 7个字节的该行回滚段指针DB_ROLL_PTR, 指向回滚的具体内容
- 6个字节的行标识DB_ROW_ID, 自增ID
- 每开启一个新的事务, DB_TRX_ID就会自动递增, 事务开始时刻的DB_TRX_ID会作为事务的版本号, 用来和查询到的每行记录的版本号进行比较
5. MySQL存储引擎
5.1 InnoDB存储引擎
InnoDB采用MVVC支持高并发, 实现了四个标准隔离级别, 其默认级别是REPEATBLE READ(可重复读), 并且通过间隙锁策略防止幻读的出现
InnoDB内部做了很多优化, 包括
- 从磁盘读取数据时采用的可预测性预读
- 能够自动在内存中创建加速读操作的自适应hash索引
- 创建缓冲区, 加速插入操作
- 支持热备份
5.2 MyISAM存储引擎
MyISAM不支持事务和行级锁, 支持表锁, 崩溃后无法安全恢复
MyISAM压缩表, 减少磁盘空间占用, 减少磁盘IO, 提高查询性能, 压缩表也支持索引, 但是索引是只读的
5.3 内建的其他存储引擎
- Archive
- Blackhole
- CSV
- Memory
- Merge
- NDB
5.4 第三方存储引擎
- OLTP
5.5 选择合适的存储引擎
默认选择InnoDB存储引擎, 特殊场景, 可以选择其他存储引擎
- 日志型应用, 这类应用对插入速度有很高的要求, 可以选择MyISAM, 因为开销低, 而且插入速度特别快
- 只读或者大部分情况只读, 如果不接MyISAM崩溃恢复的问题, 选用MyISAM是合适的
- 事务型系统, InnoDB是最佳选择
- 大数据量, 采用InnoDB, 提前做好系统规划, 合理选择硬件, 做好物理设计