1.并发控制
并发控制目的是当多个连接对数据库进行修改时保证数据的一致性。MySQL提供两个级别的并发控制:服务器级和存储引擎级。
1.1 读写锁
从功能上可以分为共享锁和排他锁,也就是我们常讲的读锁和写锁。简单描述就是:读锁是共享的,
或者说是互相不阻塞的。多个用户在同一时刻可以同时读取统一资源,而互不干扰。写锁则是排他的,
也就是说一个写锁会阻塞其他的写锁和读锁。
1.2 锁粒度:锁策略
从粒度上分类:
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。一般ddl使用。表锁分为表共享读锁(共享锁)和表独占写锁(排他锁)。
表共享读锁用法
LOCK TABLE ... READ;
表独占写锁用法
LOCK TABLE... WRITE;
解锁表用法
UNLOCK TABLES;
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。行级锁分为共享锁和排他锁。
行共享锁用法
SELECT ... LOCK IN SHARE MODE;
行排他锁用法
SELECT ... FOR UPDATE;
具体锁定哪行,后面加where条件即可。我们现在数据库表都用的innodb,innodb支持行级锁,如果换成myisam就不支持行级锁。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
2.事务
并发事务带来的问题
-
脏读(Dirty Reads):A事务读取B事务尚未提交的更改数据,并在这个数据基础上操作。如果B事务回滚,那么A事务读到的数据根本不是合法的,称为脏读。
-
不可重复读(Non-Repeatable Reads):A事务读取了B事务已经提交的更改(或删除)数据。比如A事务第一次读取数据,然后B事务更改该数据并提交,A事务再次读取数据,两次读取的数据不一样。
-
幻读(Phantom Reads):A事务读取了B事务已经提交的新增数据。注意和不可重复读的区别,这里是新增,不可重复读是更改(或删除)。
-
更新丢失(Lost Update):第一类丢失更新:A事务撤销时,把已提交的B事务的数据覆盖掉。第二类丢失更新:A事务提交时,把已提交的B事务的数据覆盖掉。
2.1事务隔离级别
隔离级别
|
脏读(Dirty Read)
|
不可重复读(NonRepeatable Read)
|
幻读(Phantom Read)
|
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
数据库实现事务隔离的方式, 基本上可以分为两种方式:
-
第一种: 在读取数据前, 对其加锁, 阻止其他事务对数据进行修改.
-
第二种: 不用加任何锁, 通过一定机制生成一个数据请求时间点的一致性数据快照.并用这个快照来提供一定级别(语句级或事务级)的一致性读取.从用户的角度来看, 好像数据库可以提供同一数据的多个版本, 因此, 这种技术叫做数据多版本并发控制(MVCC).
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行.
3.多版本并发控制
第一点:
MVCC并不是MySql独有的,Oracle,PostgreSQL等都在使用。
MVCC并没有简单地使用行锁,而是使用“行级别锁”(row-level locking)。
MVCC的基本原理是:
MVCC的实现,通过保存数据在某个时间点的快照来实现的。这意味着一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图。根据事务开始的时间不同,同时也意味着在同一个时刻不同事务看到的相同表里的数据可能是不同的。
MVCC的基本特征:
- 每行数据都存在一个版本,每次数据更新时都更新该版本。
- 修改时Copy出当前版本随意修改,各个事务之间无干扰。
- 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)
InnoDB存储引擎MVCC的实现策略:
InnoDB MVCC提供了两个关键功能,一:写不阻塞读 。 二:读一致性
在每一行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号(可能为空)。这里的版本号并不是实际的时间值,而是系统版本号(InnoDB engine 有一个全局的 Transaction ID)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。
每个事务又有自己的版本号,这样事务内执行CRUD操作时,就通过版本号的比较来达到数据版本控制的目的。具体做法见下面的示意图。
MVCC具体的操作如下:
SELECT:InnoDB会根据以下两个条件检查每行记录:
1)InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,只么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
2)行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。
INSERT:InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
DELETE:InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE:InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当系统的版本号为原来的行作为删除标识。
保存这两个额外系统版本号,使大多数操作都可以不用加锁。这样设计使得计数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
MVCC只在REPEATABLE READ和READ COMMITED两个隔离级别下工作,其它两个隔离级别和MVCC不兼容。
4.常见锁策略
锁的策略 | 并发性 | 开销 | 引擎 |
表 | 最低 | 最低 | MyISAM,Merge,Memory |
行 | 高 | 高 | NDB Cluster |
行和MVCC | 最高 | 最高 | InnoDB,Falcon,PBXT,solidD |