文章目录
1.1 MySQL逻辑架构
1.2 并发控制
1、读写锁
- 读锁/共享锁:相互不阻塞。多个客户在同一时刻可以同时读取同一个资源。
- 写锁/排它锁:一个写锁会阻塞其他的写锁和读锁。
2、锁粒度
在任何时候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突即可。
- 表锁: 表锁是MySQL中最基本的锁策略,并且是开销最小的策略。 在特定的场景下,表锁也有良好的性能(如,READ LOCAL表锁支持某些类型的并发写操作)。另外,写锁比读锁有更高的优先级(即一个写锁请求可能会被插入到读锁队列的前面)。
- 行级锁:行级锁可以最大程度地支持并发处理(同时也带来了最大的锁开销)。行级锁只在存储引擎层实现,而MySQL服务器层没有实现。
1.3 事务
事务就是一组原子性的SQL查询,或者说一个独立的工作单位。
ACID:原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)。
- 原子性:整个事务中的所有操作要么全部提交成功,要么全部失败回滚。
- 一致性:数据库总是从一个一致性状态转换到另一个一致性的状态。
- 隔离性:并发时,事务间相互不影响。
- 持久性:一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。
1.3.1 隔离级别
- 脏读:事务B可以读到事务A没有提交的数据。隔离级别READ COMMITTED以上可以避免。
- 不可重复读:事务B在事务A不断读取的情况下做了修改,导致事务A每次读到的数据不一致。隔离级别REPEATABLE READ以上可以避免。REPEATABLE READ是MySQL默认的事务隔离级别。
- 幻读:事务A读取与搜索条件匹配的若干行,事务B以删除或插入等方式修改A的结果集,导致事务A再进行操作时行数发生变化。隔离级别SERIALIZABLE可以避免。
- 加锁读:SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用问题。实际应用中很少使用SERIALIZABLE这个隔离级别,只有在非常需要确保数据一致性而且可以接受没有并发的情况下,才考虑使用该级别。
1.3.2 死锁
- 定义:死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。
- 原因:锁的行为和顺序和存储引擎有关。
- 有些是因为真正的数据冲突,这种情况难以避免。
- 有些完全是由于存储引擎的实现方式导致的。
- 解决:
- 复杂的系统(如InnoDB存储引擎)能检测到死锁的循环依赖,并立即返回一个错误。
- 当查询的时间到达锁等待超时的设定后放弃锁请求(不推荐)。
- InnoDB目前处理的方法是:将持有最少行级排他锁的事务进行回滚。
1.3.3 事务日志
- 使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为以追加的方式记录到持久在硬盘的事务日志中,内存中被修改的数据在后台慢慢地被刷回到磁盘,即预写式日志。
- ACID中持久化的实现:如果数据的修改已经记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够根据事务日志自动恢复这部分修改的数据。
1.3.4 MySQL中的事务
MySQL中提供了两种事务型的存储引擎:InnoDB和NDB Cluster。
- 自动提交(AUTOCOMMIT):MySQL默认采用自动提交模式。如果不是显示地开始一个事务,则每个查询都被当作一个事务执行提交操作。
- 在事务中混合使用存储引擎:事务是由下层的存储引擎实现的,所以在同一个事务中,使用多种存储引擎是不可靠的。在事务中混合使用了事务型和非事务型的表,在正常提交的情况下不会有什么问题,但是需要回滚时,非事务型的表上的操作就无法撤销,会导致数据库处于不一致的状态。在非事务型的表上执行事务相关操作时不会发出提醒,只会在回滚时发出警告。
- 隐式和显式锁定:
- InnoDB会根据隔离级别在需要的时候自动加锁。InnoDB采用的是两阶段锁定协议。在事务执行过程中,随时都可以锁定,锁只有在执行COMMIT或者ROLLBACK的时候才会释放,并且所有的锁在同一时刻被释放。
- InnoDB支持通过特定的语句进行显式锁定(这种锁定提示经常被滥用,实际上应当尽量避免使用):
select ... lock in share mode <!--显式加读锁-->
select ... for update <!--显式加写锁-->
1.4 多版本并发控制(MVCC)
- MVCC是行级锁的一个变种,是通过保存数据在某个时间点的快照来实现的,具有乐观和悲观两种并发控制,MVCC没有正式的规范,各个存储引擎和数据库系统的实现都是各异的。。
- InnoDB的MVCC是通过在每行记录后保存两个隐藏的列来实现,一个保存了行的创建时间,一个保存行的过期时间(或删除时间),这个时间是即系统版本号。每开始一个新事物,系统版本号都会自动递增,事务开始时刻的系统版本号会作为事务的版本号。
- select:只有符合下述两个条件的记录才能返回作为查询结果:
- InnoDB只查找版本早于当前事务版本的数据行,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或修改过的。
- 行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除。
- insert:InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
- delete:InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
- update:InnoDB为插入一行新记录保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
- select:只有符合下述两个条件的记录才能返回作为查询结果:
1.5 MySQL的存储引擎
- MyISAM适用场景:
- 频繁执行全表count语句(有一个变量保存)
- 对数据进行增删改频率不高,查询非常频繁(数据、文件分离)
- 没有事务
- InnoDB适用场景:
- 数据增删改查都相当频繁(行级锁)
- 可靠性要求高,要求支持事务