MySQL体系结构
总的来说, MySQL可以看成是二层架构, 第一层是SQL层, 在MySQL数据库系统处理底层数据之前的所有工作都是在这一层完成的, 包括权限判断SQL解析、执行计划优化、Query Cache的处理等等. 第二层是存储引擎层, 也就是底层数据存取操作的实现部分, 由多种存储引擎共同组成.
各模块工作流程
MySQL的存储引擎
MySQL使用插件式的存储引擎结构, 可以根据不同的业务场景选择不同的存储引擎. MySQL最常用的存储引擎是InnoDB和MyISAM, 两者的区别如下:
对比项 | InnoDB | MyISAM |
---|---|---|
外键 | 支持 | 不支持 |
事务 | 支持 | 不支持 |
行表锁 | 行锁, 适合高并发操作 | 表锁, 不合适高并发操作 |
缓存 | 缓存索引和真实数据, 对内存要求较高 | 只缓存索引 |
表空间 | 大 | 小 |
关注点 | 事务 | 性能 |
默认安装 | 是 | 是 |
在大数据量,高并发量的互联网业务场景下,请使用InnoDB:
- 事务,对数据一致性帮助很大
- 行锁,对提高并发帮助很大
事务
事务的特性
- 原子性(atomicity)
一个事务必须被视为一个不可分割的最小工作单元, 整个事务中的所有操作要么全部执行成功, 要么全部失败回滚, 对于一个事务来说, 不可能只执行其中的一部分操作. - 一致性(consistency)
事务必须使数据库从一个一致性的状态变换到另一个一致性的状态, 也就是说一个事务执行之前和执行之后都必须处于一致性的状态. 拿转账来说, 假设用户A和用户B两者的钱加起来一共是5000, 那么不管A和B之间如何转账, 转几次账, 事务结束后两个用户的钱相加起来应该还得是5000, 这就是事务的一致性. - 隔离性(isolation)
并发的事务之间是相互隔离的, 一个事务所做的修改在最终提交之前, 对其他事务是不可见的. 例如, 账户A有5000元存款, 执行完转账语句(-500), 只要该事务没有提交, 对其他事务来说账户余额还是5000元. - 持久性(durability)
事务一旦提交, 其对数据库的修改就是永久性的, 即使系统崩溃, 修改的数据也不会丢失.
并发事务带来的问题
- 更新丢失(Lost Update)
多个事务修改同一行记录, 后面的修改覆盖了前面的修改. - 脏读(Dirty Reads)
一个事务可以读取另一个事务未提交的数据. - 不可重复读(Non-Repeatable Reads)
同一个事务中执行两次相同的查询, 可能得到不一样的结果. 这是因为在查询间隔内,另一个事务修改了该记录并提交了事务. - 幻读(Phantom Reads)
当某个事务在读取某个范围内的记录时, 另一个事务又在该范围内插入了新的记录, 当之前的事务再次读取该范围的记录时, 会产生幻行.
隔离级别
在MySQL常用的存储引擎中, 只有InnoDB支持事务, 所以这里说的隔离级别指的是InnoDB下的事务隔离级别.
- READ UNCOMMITTED(读未提交)
在该隔离级别, 事务中的修改即使没有提交, 对其他事务也都是可见的. 避免了更新丢失的发生. - READ COMMITTED(读已提交)
在该隔离级别, 一个事务只能看见已经提交的事务所做的修改. 避免了更新丢失和脏读. - REPEATABLE READ(可重复读)
MySQL默认的隔离级别, 该级别保证了在同一个事务中多次读取同样的记录的结果是一致的. 避免了更新丢失、脏读、不可重复读和幻读. (注意看MySQL官网, RR隔离级别下解决了幻读问题) - SERIALIZABLE(可串行化)
SERIALIZABLE是最高的隔离级别, 它通过强制事务串行化执行, 避免了并发事务带来的问题.
隔离级别 | 读数据一致性 | 更新丢失 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|---|
读未提交 | 最低级别, 只能保证不读取物理上损坏的数据 | × | √ | √ | √ |
读已提交 | 语句级 | × | × | √ | √ |
可重复读 | 事务级 | × | × | × | × |
可串行化 | 最高级别, 事务级 | × | × | × | × |
MySQL锁机制
MySQL有三种类型的锁:
- 表锁: 开销小, 加锁快, 不会出现死锁; 锁定粒度大, 发生锁冲突的概率最高, 并发度最低.
- 行锁: 开销大, 加锁慢, 会出现死锁; 锁定粒度最小, 发生锁冲突的概率最低, 并发度也最高.
- 页锁: 开销和加锁时间介于表锁和行锁之间, 会出现死锁; 锁定粒度介于表锁和行锁之间, 并发度一般.
表锁
MyISAM存储引擎使用的是表锁, 表锁有两种模式: 读锁(read lock)和写锁(write lock), 也叫共享锁(shared lock)和排他锁(exclusive lock). 读锁和写锁的关系如下:
- 某一事务对表进行读操作(加读锁), 不会阻塞其他事务对同一表的读操作, 但会阻塞写操作. 只有当读锁释放后, 其他事务才能执行写操作.
- 某一事务对表进行写操作(加写锁), 会阻塞其他事务对同一表的读和写操作, 只有当写锁释放后, 才会执行其他事务的读写操作.
是否兼容 | 读锁(共享锁) | 写锁(排他锁) |
---|---|---|
读锁(共享锁) | 是 | 否 |
写锁(排他锁) | 否 | 否 |
MyISAM存储引擎在执行查询操作之前, 会自动给涉及的所有表加读锁, 在执行更新操作(update、delete、insert)之前, 会自动给涉及的表加写锁. 这个过程并不需要用 lock table 命令给表显示加锁.
MyISAM存储引擎在自动加锁时, 总是一次性获得 sql 语句所需要的全部锁, 所以显示锁表的时候, 必须同时给所有涉及的表加锁. 这也是 MyISAM 表不会出现死锁(deadlock)的原因.
行锁
InnoDB存储引擎使用的是行锁, 行锁的共享锁和排他锁与表锁类似, 差别在于行锁作用于某一行记录.
关于InnoDB锁的详细介绍, 看这篇文章: InnoDB并发控制