1.3-数据库概念
Create by fall on 13 Oct 2025 Recently revised in 13 Oct 2025
数据库底层概念,实现一个数据库的相关理论,以及解决编写一个数据库时会遇到的问题
数据库概念
并发控制
并发控制,读取数据过程中,数据源发生了更改,数据库必须来保证事务之间的隔离,来避免某个事务因为未见最新的数据而造成的误操作。
主流的三种并发控制技术分别是:
- Multi-version Concurrency Control (MVCC)
- Strict Two-Phase Locking (S2PL)
- Optimistic Concurrency Control (OCC)
其中并发控制最简单的方式就是加锁访问。加锁会将读写操作串行化,避免出现不一致的状态;但是读操作会被写操作阻塞,大幅降低读性能。
MVCC,在 MVCC 中,每次写操作都会在旧的版本之上创建新的版本,并且会保留旧的版本。当某个事务需要读取数据时,数据库系统会从所有的版本中选取出符合该事务隔离级别要求的版本。
- 保证了事务周期内数据的一致性。
- 提高了数据库的并发性能,解决读-写的阻塞问题,写-写依然还是阻塞的。
锁管理器:
在根据加锁的范围(Lock Granularity),可以分为:全局锁、表级锁、行锁。
- 全局锁会把整个数据库实例加锁,将使数据库处于只读状态,其他数据写入和修改表结构等语句会阻塞,一般在备库上做全局备份使用。
- 表级锁有两种,一种是表锁,和读写锁一样,另外一种是元数据锁,也叫意向锁,不需要显示申明,当执行修改表结构,加索引的时候会自动加元数据写锁,对表进行增删改查的时候会加元数据读锁。这样当两条修改语句的事务之间元数据锁都是读锁不互斥,但是修改表结构的时候执行更新由于互斥就需要阻塞。
- 还有一种行级锁称为间隙锁,他锁定的是两条记录之间的间隙,防止其他事务往这个间隙插入数据,间隙锁是隐式锁,是存储引擎自己加上的。
从锁的策略上,可以分为共享锁与排他锁:
- 共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
- 排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
乐观锁(Optimistic Lock)与悲观锁(Pessimistic Lock),而MVCC(Multiple Version Concurrency Control)这样的基于数据版本的锁则是乐观锁,它能够保证读写操作之间不会相互阻塞:
- 每个事务都可以在同一时间修改相同的数据;
- 每个事务会保有其需要的数据副本;
- 如果两个事务修改了相同的数据,那么仅有单个更改操作会被接收,另一个操作会被回滚或者重新执行。
乐观锁,大多是基于数据版本(Version)记录机制实现。数据版本即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 version 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。
隔离级别
- 脏读:一个客户端读取到另一个客户端尚未提交的写入。读已提交或更强的隔离级别可以防止脏读。
- 脏写:一个客户端覆盖写入了另一个客户端尚未提交的写入。几乎所有的事务实现都可以防止脏写。
- 读取偏差(不可重复读):在同一个事务中,客户端在不同的时间点会看见数据库的不同状态。譬如B事务读到了A事务已经提交的数据,即B事务在A事务提交之前和提交之后读取到的数据内容不一致(AB事务操作的是同一条数据);快照隔离经常用于解决这个问题,它允许事务从一个特定时间点的一致性快照中读取数据。快照隔离通常使用多版本并发控制(MVCC)来实现。
- 更新丢失:两个客户端同时执行读取-修改-写入序列。其中一个写操作,在没有合并另一个写入变更情况下,直接覆盖了另一个写操作的结果。所以导致数据丢失。快照隔离的一些实现可以自动防止这种异常,而另一些实现则需要手动锁定(SELECT FOR UPDATE)。
- 写偏差:一个事务读取一些东西,根据它所看到的值作出决定,并将决定写入数据库。但是,写入的时候,决定的前提不再是真实的。只有可序列化的隔离才能防止这种异常。
- 幻读:事务读取符合某些搜索条件的对象。另一个客户端进行写入,影响搜索结果。譬如B事务读到了A事务已经提交的数据,即A事务执行插入操作,B事务在A事务前后读到的数据数量不一致。快照隔离可以防止直接的幻像读取,但是写入歪斜环境中的幻影需要特殊处理,例如索引范围锁定。
数据库