并发控制
无论何时,只要有多个查询需要在同一时刻修改数据,都会产生并发控制的问题。讨论MySQL在两个层面的并发控制:服务器层和存储引擎层。
读写锁
在处理并发读或者写时,可以通过一个由两种类型实现的锁组成的锁系统来解决问题。
这两种类型的锁通常被称为共享锁和排他锁,也称为读锁和写锁。
读锁 |
共享的 |
写锁 |
排他的 |
读锁是共享的,或者说是相互不阻塞的,多个客户在同一时刻可以读取同一资源,而不互相干扰。写锁则是排他的,也就是说一个写锁会阻塞其他的写锁和读锁,这是出于安全策略的考虑,只有这样,才能确保在给定的时间内,只有一个用户能执行写入。
锁粒度
提高共享资源并发性的方式是让锁定的对象更加有选择性,尽量只锁定需要修改的部分数据,而不是所有的资源,更理想的方式是只对会修改的数据片进行精确的锁定。加锁也需要消耗资源,锁的各种操作,包括获得锁,检查锁是否已经解除、释放锁都会增加系统的开销。
表锁 |
开销最小 |
行级锁 |
并发处理最强 |
多版本并发控制
MVCC是行级锁的一个变种,在很多情况下避免了加锁操作,具体的实现是通过保存数据在某个时间点的快照来实现的。实现方式有乐观锁和悲观锁两种方式。InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间。存储的不是实际的是时间值,而是系统的版本号。每开始一个新的事务,系统版本号都会自动递增。
mysql中的事务
mysql默认采用自动提交模式,也就是说,如果不是明显地开始一个事务,每个查询都会被当做一个事务执行提交操作,在当前连接中,可以通过设置AUTOCOMMIT变量来启动或者禁用自动提交模式,1表示启动
事务就是一组原子性的SQL查询,或者说是一个独立的工作单元。事务内的语句要么全部执行,要么全部执行失败,体现事务的原子性
事务的一致性是指数据库总是从一个一致性的状态转移到另外一个一致性的状态
事务的隔离性是指一个事务在最终提交以前,对其他的事务是不可见的
事务的持久性是指一旦事务被提交,则其所做的修改会永久保存到数据库中
事务的隔离级别
未提交读
在未提交读级别中,事务中的修改,即使没有提交,对其他事务也都是可见的,事务可以读取未提交的数据,也被称为脏读,在实际应用中一般很少用到
提交读
提交读是大多数数据库系统默认的隔离级别,但MySQL不是,具体是指一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的,也被称为不可重复读
可重复读
可重复读解决了脏读的问题,该级别保证了在同一个事务中多次读取同样记录的结果是一致的,无法解决幻读的问题,幻读是指当某个事务在读取某个范围内的记录时
另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行,InnoDB通过多版本的控制解决了幻读的问题,mysql默认的隔离级别
可串行化
可串行化是最高的隔离级别,它通过强制事务串行执行,避免了前面说的幻读的问题,简单说就是可串行化会在每一行的数据上都加锁,所以可能导致大量的超时和锁争用的问题
隔离级别 |
脏读可能性 |
不可重复读可能性 |
幻读可能性 |
加锁读 |
未提交读 YES YES YES NO 提交读 NO YES YES NO 可重复读 NO NO YES NO 可串行化 NO NO NO YES |
死锁
死锁是指两个或者多个事务在一个资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就会发生死锁。多个事务同时锁定一个资源时,也会发生死锁。为了解决这个问题,数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统例如InnoDB存储引擎,越能检测到死锁的循环依赖,并立即返回一个错误。还有一种解决方式就是当查询的时间达到锁等待超时的设定后放弃锁请求,这种方式通常来说不太好。
InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。
锁的行为和顺序是和存储引擎相关的。以同样的顺序执行语句,有些存储引擎会产生死锁,有些则不会。
死锁产生有双重原因:有些是因为真正的数据冲突,这种情况很难避免,有些则完全是由于存储引擎的实现方式导致的。死锁发生以后,只有部分或者完全回滚其中的一个事务,才能打破死锁。对于事务型的系统,这是无法避免的。