Photo by Ivana Cajina on Unsplash
高性能 MySQL 读书笔记 - 架构和历史
author's avatar
igaozp
未来已来,只是分布不均

并发控制

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)来控制。

乐观锁假设不会发生并发冲突,只在提交操作时检查数据是否被其他人修改过,适用于读多写少的应用场景。

悲观锁假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作,每次在读取数据的时候都会上锁。乐观锁大多使用版本号进行控制。