MySQL架构与历史
1.1 MySQL架构
1.2 并发与控制
MySQL的并发控制包括两个层面:服务器层和存储引擎层
-
读写锁(也叫共享锁和排他锁)
-
锁粒度
- 表锁(一般是服务器层控制,例如alter table语句)
- 行级锁(服务器层完全不了解,行级锁由各自的存储引擎层实现)
1.3 事务
事务就是一组原子性的SQL查询,或者说一个独立的工作单元。
-
事务的ACID概念:
- 原子性(atomicity):事务不可分割,在执行过程中,要么全部成功,要么全部失败;
- 一致性(consistency):数据库总是从一个一致性状态转换到另外一个一致性的状态;
- 隔离性(isolation):通常来说,一个事务所做的修改在最终提交以前,对其他的事务是不可见的;
- 持久性(durability):一旦事务提交,则其所做的修改就会永久保存到数据库中;
-
隔离级别:
隔离性其实比想象的要复杂。在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事物内和事物间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。
- READ UNCOMMITTED(未提交读):一个事务可以读到另一个事务未提交修改的数据,这也被称为脏读(Dirty Read)
- READ COMMITTED(已提交读):一个事务开始时,只能“看见”已提交事物所做的修改。因为两次查询可能会得到不一样的结果,所以会产生不可重复读问题。例如a事务第一次读的数据是1,此时b事务将值修改为a+1,此时当a再次去读数据的时候变成了a+1,两次读取的数据不一样。
- REPEATABLE READ(可重复读):该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读的问题。例如a事务第一次读取的范围数据1,3,此时b事务插入了一条数据2,此时当a再次去读范围内的数据的时候变成了1,2,3,会产生幻行(Phantom Row)
- SERIALIZABLE(可串行化):是最高的隔离级别。它通过强制事物串行执行,避免了前面说的幻读问题。
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 |
---|---|---|---|
READ UNCOMMITTED | Yes | Yes | Yes |
READ COMMITTED | No | Yes | Yes |
REPEATABLE READ | No | No | Yes |
SERIALIZABLE | No | No | No |
-
死锁:死锁是指两个或多个事物在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。如下面两个事务,如果两个事务恰巧都更新了第一条数据,同时也锁定了该行数据,接着每个事务都尝试去执行第二条update语句,却发现该行为已被锁定,然后两个事务都在等对方释放锁,同时又持有对方需要的锁,则陷入死循环。
-
事物1:
start transaction; update stock_price set close = 45.50 where stock_id = 4 and date = '2002-05-01'; update stock_price set close = 19.80 where stock_id = 3 and date = '2002-05-02'; commit;
-
事物2:
start transaction; update stock_price set close = 20.12 where stock_id = 3 and date = '2002-05-02'; update stock_price set close = 47.20 where stock_id = 4 and date = '2002-05-01'; commit;
InnoDB存储引擎,能检测到死锁的循环依赖,并立即返回一个错误。还有一种解决方式,就是当查询时间达到锁等待超时的设定后放弃锁请求。InnoDB目前处理死锁的方法是,将持有最少行级排它锁的事务进行回滚(这是相对比较简单的死锁回滚算法)。
-
-
事务日志
事务日志可以帮助提高事务的效率。使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。事务日志持久化后,内存中被修改的数据在后台可以慢慢地回刷到磁盘。目前大多数存储引擎都是这样实现的,我们通常称之为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘。
-
MySQL中的事务
MySQL事务型存储引擎有两种:InnoDB和NDB Cluster。
- 自动提交(AUTOCOMMIT):mysql默认采用自动提交模式,也就是说,如果不显示地开始一个事务,则每个查询都被当作一个事务执行提交操作,对于非事务型存储引擎,修改autocommit不会有任何影响。一些DDL和LOCK TABLES等语句还会强制执行commit提交当前的活动事务。
- 为每张表选择合适的存储引擎(在同一个事务中使用多种存储引擎是不可靠的)
- 隐式和显式锁定:通常InnoDB会根据隔离级别在需要的时候自动隐式加锁,另外InnoDB也支持显式锁定(应尽量避免使用)
1.4 多版本并发控制
-
由于MySQL行级锁并发性能不高,为了提升并发性能,大多数事务型存储引擎一般都实现了多版本并发控制(MVCC),可以认为MVCC是行级锁的一个变种,但是它在很多情况下都避免了加锁操作,因此开销更低。
-
在InnoDB的MVCC中,是通过在每行记录后面保存两个隐藏列来实现的。一个是创建时间,一个是过期时间(或删除时间)。当然存储的不是实际的时间值,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。在RR隔离级别下,MVCC具体操作如下:
-
select:
InnoDB会根据以下两个条件检查每行记录:
a、InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
b、行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前都未被删除。
只有复合上述两个条件的记录,才能返回作为查询结果。
-
insert:
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
-
delete:
InnoDB为删除的每一行保存当前系统版本号作为删除标识。
-
update:
InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
-
优点:大多数读操作都可以不用加锁,使得读操作简单,性能好
缺点:需要额外的存储空间,需要更多的行检查工作以及一些额外的维护工作。
1.5 MySQL的存储引擎
- InnoDB
- MySQL默认引擎,支持事务、行级锁,MVCC
- MyISAM
- 不支持事务和行级锁、宕机后无法安全恢复
- ....