并发控制
MySQL 中只要存在多个查询同时修改数据的场景,就会产生并发控制的问题。MySQL 通常在两个层面进行并发控制,一是服务器层面,二是存储引擎层面。
读写锁
并发控制中一类经典的问题就是:如何避免对同一条数据进行并发地修改和查询操作,以避免产生不可预测的结果。
这里可以通过构建一个锁系统来解决,这个系统有两种锁构成:共享锁(shared lock
)和排他锁(exclusive lock
),也被称为读锁(read lock
)和写锁(write lock
)。
通常读锁是共享的,意味着多个客户端可以同时读取同一数据资源,因为读操作不会对数据产生副作用,数据不会产生变化。 而写锁是排他的、非共享的,一个写锁会阻塞其他的写锁和读锁,只有这样才能保证其他的写操作和当前的写操作不会产生冲突,读操作不会读取到正在写入的内容。
这些锁定操作在 MySQL 内部频繁地发生着,MySQL 会自动管理这些锁的锁定和释放,对我们开发者来说是透明的,无需去干预。
锁粒度
提高共享资源并发性的一种方式是让锁定的对象更具有选择性,尽可能对数据精确地锁定,锁定的数据越少,系统的整体并发程度越高。 但是锁的管理也需要消耗系统资源,为了精确地锁定资源系统可能会额外消耗资源去检索定位数据,系统的整体性能也会收到影响。
所以需要一种策略去平衡锁的开销和数据的安全性。
表锁
表锁是 MySQL 中最基本的锁策略,且性能开销最小。 用户对表进行写操作时(插入、删除、更新)需要获得写锁锁定整张表,阻塞其他用户对该表的所有读写操作。 当没有写锁时才能获取到读锁,且读锁之间不会相互阻塞。另外,写锁比读锁有着更高的优先级。
行级锁
行级锁能够最大程度地支持并发处理,同时也意味着在锁处理上会带来最大程度的性能开销。与表锁不同行级锁只能由存储引擎来提供实现。
事务
说到数据库就不能不提事务,简单来说事务内的语句要么全部执行成功,要么全部失败。
事务必须具备原子性(atomicity
)、一致性(consistency
)、隔离性(isolation
)、持久性(durability
),即ACID。
MySQL 默认采用自动提交事务,即每个查询都被当作一个事务执行并提交。
隔离级别
| 隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | | ------------------------- | ---------- | ---------------- | ---------- | --- | | READ UNCOMMITTED | YES | YES | YES | NO | | READ COMMITTED | NO | YES | YES | NO | | REPEATABLE READ (DEFAULT) | NO | NO | YES | NO | | SERIALIZABLE | NO | NO | NO | YES |
何为脏读,简单来说指一个事务中访问到了另外一个事务未提交的数据。
又何为幻读,即一个事务读取两次,但得到的记录条数不一致,如果记录的数据不一致,那则称为不可重复读。
死锁
死锁是指多个事务在同一资源上相互占用,并且请求锁定对方占用的资源,从而导致恶性循环的现象。
InnoDB
目前解决死锁的方法为:将持有最少行级排他锁的事务进行回滚。
多版本并发控制
多版本并发控制(MVCC
)是行级锁的一个变种,在很多情况下避免了加锁操作,且写操作只锁定必要的行,MVCC
通过保存数据在某个时间点的快照来实现。
MVCC
一般由乐观锁(optimistic lock
)和悲观锁(pessimistic lock
)来控制。
乐观锁假设不会发生并发冲突,只在提交操作时检查数据是否被其他人修改过,适用于读多写少的应用场景。
悲观锁假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作,每次在读取数据的时候都会上锁。乐观锁大多使用版本号进行控制。