mysql的逻辑架构
1.读锁(Read Lock) /写锁(Write Lock)
例子:读取邮箱信息并不麻烦,即使多个用户并发读取同一邮箱,也不会有什么问题。因为读操作不会造成任何修改,所以就不会出错。不过,假如一个程序正在读邮箱,另一个用户试图删除编号25的邮件,那将发生什么结果?结果可能是,某一正在读的用户报错退出,或者是他看到幅与邮箱的实际状态不符的错误视图。所以为了安全起见,即使读邮箱也必须特别注意。
可以把邮箱想象成数据库,把每封邮件想象成表中的行,就很容易发现,在这类场景里,问题都是类似的,如修改数据库表中的行,就十分类似于删除或修改邮箱文件中的邮件信息。
解决这类经典问题的方法是使用并发控制,并发控制的概念是很简单的。在处理并发读或并发写时,系统会使用一套锁系统来解决问题。这种锁系统由两类锁组成,通常称之为共享锁(Shared Lock)和排他锁(Exclusive Lock),或者叫读锁(Read Lock)和写锁(Write Lock)。
不用关心锁技术的具体实现方式,在这里描述相关锁概念如下:
某一资源上的读锁是共享的,或是说是互不阻塞的。在同一时间,多个用户可以读取同一资源,而互不干扰。另一方面,写锁是排他的,也就是说,一个写锁会阻塞其他的读锁和写锁,这是出于安全策略的考虑,在给定时间里,只有一个用户能写人资源,以防止用户在写操作的同时其他用户读取同一资源。
对数据库来说,随时随地都会发生锁定。当某一用户修改某一部分数据时,MySQL会禁止其他用户读取同一数据。大多数时候,MySQL都是以透明的方式实现锁的内部管理。
2.锁粒度( Lock Granularity))
一种提高共享资源并发性的方法就是让锁定对象更有选择性。要记住只锁定部分须修改的数据,而不是所有的资源。更理想的方式是,只对要修改的数据片精确加锁。任何时间,在给定的资源上,被加锁的数据量越小,就可以允许更多的井发修改,只要相互之间互不冲突即可。
这么做的间题是加锁也会消耗系统资源。每一种锁操作,如获得锁、检查锁是否已解除,以及释放锁等,都会增加系统的开销。如果系统花费大量时间来管理销,而不是读写数据,那么系统整体性能可能会因此受到影响。
所谓的锁策略,就是在锁开销和数据安全之间寻求一种平衡,这种平衡也能影响系統性能。大多数的商业数据库服务器没有提供更多的选择,通常都是在表上施加行级锁(Row- Level Locking),并提供种种复杂的手段,在有锁的情况下改善系统的性能,
而另一方面, MYSQL则提供了多种选择。每种 MYSQL存储引都可以实现独有的锁策略( Locking Policy)或 锁粒度( Lock Granularitey),在存储引擎设计中锁管理( Lock Management)是个非常重要的议题。将锁粒度调整到某一水平,也许就能为某种应用目的提供更佳的性能,不过,这也可能使存储引綮又不适用于其他的用途了。由于 MYSQL可以提供多种存储引,所以它不需要一个通用解决方案。下面将介绍两种最重要的锁策略
表锁( Table lock)
MYSQL支持大多数基本的锁策略,其中开销最小的锁策略是表锁。表锁类似于前文所述的邮箱加锁机制:它将整个表加。当一个用户对表进行写操作(如插入、野除、更新)时,用户可以获得一个写锁。写锁会禁止其他向用户的读写操作。另外,只有无人做写操作时,用户才能获得读锁,读锁之间是互不神突的
在特定的环境中,表锁可能性能良好。例如, READ LOCAL表锁支持某种类型的并发写操作。另外,写锁比读 有更高的优先级,即使有读操作用户已排在队列中,一个被中请的写锁仍可以排列在锁队列的前列(写锁会被安置在读锁之前,而读镇不能排在写锁之前)。
最然存储引擎管理自己的锁, MYSQL本身也能使用各种有效的表锁,以用于各种目的。例如, MYSQL服务器可以在语句中,如 ALTER TABLE语句中,使用表锁、而不用考虑存储引擎。
行级锁( Row locks)
行级锁可以支持最大的并发处理(同时也带来最大的锁开销)。众所周知,行级锁在 INNODB 和 Falcon 存储引擎中已得以实现,在其他一些存储引擎也有实现。行级锁由存储引擎实现,而不是由 MYSQL 服务器实现,服务器完全不了解存储引擎里的锁实现方式。
3.事务
ACID代表了原子性( Atomicity)、一致性( Consistency)隔离性( Isolation)和持久性( Durability)。这些概念与事务的处理标准密切关联,一个有效的事务处理系统必须满足相关标准。
原子性( Atomicity)
一个事务必须被视为一个单独的内部“不可分”的工作单元,以确保整个事务要么全部执行,要么全部回滚。当一个事务具有原子性时,该事务绝对不会被部分执行,要么完全执行,要么根本不执行。
一致性( Consistency)
数据库总是从一种一致性状态转换到另一种一致性状态。因为最终事务根本没有被提交,任何事务处理过程中所做的数据改变,也不会影响到数据库的内容。
隔离性( Isolation)
当某个事务的结果只有在完成之后才对其他事务可见。在上述例子中,当数据库执行完第3条语句,还未执后文讨论隔离级时,读者就会理解为什么我们所说的通常是“**不可见”( Invisible)**的。
持久性( Durability)
一旦一个事务提交,事务所做的数据改变将是永久的。这意味着数据改变已被记录,即使系统崩溃,数据也不会丢失。
4.隔离级
隔离的问题比想象的要复杂。SQL标准定义了4类隔离级,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
下面简单介绍四种隔离级:
READ UNCOMITTED (读取未提交内容)
在READ UNCOMMITTED隔离级,所有事务都可以“看到”未提交事务的执行结果。在这种级别上,可能会产生很多问题,除非用户真的知道自己在做什么,并有很好的理由选择这样做。本隔离级很少用于实际应用,因为它的性能也不比其他级别好多少,而别的级别还有其他更多的优点。读取未提交数据,也被称之为“脏读”(Dirty Read)。
READ COMMITTED (读取提交内容)
大多数数据库系统的默认隔离级是READ COMMITTED (但这不是MySQL默认的!)。它满足了隔离的早先简单定义:一个事务在开始时,只能“看见”已经提交事务所做的改变,一个事务从开始到提交前,所做的任何数据改变都是不可见的,除非已经提交。这种隔离级别也支持所谓的“不可重复读”(Nonrepeatable Read),这意味着用户运行同一语句两次,看到的结果是不同的。
REPEATABLE READ (可重读)
REPEATABLE READ隔离级解决了READ UNCOMMITTED隔离级导致的问题。它确保同一事务的多个实例在并发读取数据时,会“看到同样的“數据行。不过理论上,这会导致另一个棘手问题:幻读(Phantom Read)。简单来说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插人了新行,当用户再读取该范围的数据行时,会发现有新的“幻影”(Phantom) 行。InnoDB 和Falcon存储引擎通过参版本并发控制(Multiversion Concurrency Control)机制解决了幻读问题。本章后面将进一步村论这部分内容。
REPEATABLE READ 是MySQL的默认事务隔离级。InnoDB 和Falcon存储引擎都遵循这种设置,可参考第6章,了解如何改变这种设置。其他一些存储引擎也以此为默认设置,不过具体设置还要看相关引擎的具体规定。
SERIALIZABLE (可串行化)
SERIALIZABLE是最高级别的隔离级,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,SERIALIZABLE 是在每个读的数据行上加锁。在这个级别,可能导致大量的超时(Timeout)现象和锁竞争(Lock Contention) 现象。作者很少看到有用户选择这种隔离级。但如果用户的应用为了数据的稳定性,需要强制减少并发的话,也可以选择这种隔离级。表1-1总结了各种隔离级及相关的缺点。
表1-1: ANSI SQL隔离级
隔离级 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
READ UNCOMITTED | 是 | 是 | 是 | 否 |
READ COMMITTED | 否 | 是 | 是 | 否 |
REPEATABLE READ | 否 | 否 | 是 | 否 |
SERIALIZABLE | 否 | 否 | 否 | 是 |
5.死锁
死锁是指两个或多个事务在同一资源上互相占用,并请求加锁时,而导致的恶性循环现象。当多个事务以不同顺序试围加锁同一资源时,就会产生死锁。任何时间,多个事务同时加锁-一个资源,-定产生死锁。
例如,设想下列两个事务同时处理stockPrice表:
事务1
START TRANSACTION;
UPDATE StockPrice SET close=45.50 WHERE stock id= 4 and date = ‘2002- 05-01’;
UPDATE StockPrice SET close=19.80 WHERE stock id= 3 and date = ‘2002 -05-02’;
COMMIT;
事务2
START TRANSACTION;
UPDATE StockPrice SET high=20.12 WHERE stock id= 3 and date = ‘2002 -05-02’;
UPDATE StockPrice SET high = 47.20 WHERE stock id= 4 and date = ‘2002-05-01’;
COMMIT;
如果很不幸凑巧,每个事务在处理过程中,都执行了第一个查询,更新了数据行,也加锁了该数据行。接着,每个事务都去试图更新第二个数据行,却发现该行已被(对方)加锁,然后两个事务都开始互相等待对方完成,陷入无限等待中,除非有外部因素介人,才能解除死锁。
为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制。对于更复杂的系统,例如InnoDB存储擎,可以预知循环相关性,并立刻返回错误。这种解决方式实际很有效,否则死锁将导致很慢的查询。其他的解决方式,是让查询达到一个锁等待超时时间,然后再放弃争用,但这种方法不够好。目前InnoDB处理死锁的方法是,回滚拥有最少排他行级锁的事务(一种对最易回滚事务的大致估算)
锁现象和锁顺序是因存储引擎而异的,某些存储引擎可能会因为使用某种顺序的语向导致死锁,其他的却不会。死锁现象具有双重性:有些是因真实的数据冲突产生的,无法避免,有些则是因为存储引擎的工作方式导致的。
如果不以部分或全部的方式回滚某个事务,死锁将无法解除。在事务性的系统中,这是个无法更改的事实。用户在设计应用时,就应考虑这种问题的处理,许多应用在事务开始时,可以做简单的判定,决定重做事务。
6.事务日志
事务日志可使事务处理过程更加高效。和每次数据一改变就更新磁盘中表数据的方式不同,存储引擎可以先更新数据在内存中的拷贝。这非常快。然后,存储引擎将数据改变记录写人事务日志,它位于磁盘上,因此具有持久性。这相对较快,因为追加日志事件导致的写操作,只涉及了磁盘很小区城上的顺序I/O (Sequential 1/O),而替代了写磁盘中表所需要的大量随机I/O (RandomI/O)。 最后,相关进程会在某个时间把表数据更新到磁盘上。因此,大多存储引擎都选用了这种技术,也是通常所说的预写式日志(Write Ahead Logging), 利用两次磁盘写人操作把数据改变写人磁盘(注3)。
如果数据更新已写入事务日志,却还未写入磁盘的表中,而发生系统崩溃,存储引擎将会在重启后恢复相关数据改变。具体的恢复方式因存储引擎而异。