如何实现数据库读一致性
(给ImportNew加星标,提高Java技能)
1 导读
2 一致性
3 数据库事务
原子性 (Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么全部不执行。
一致性 (Consistency):几个并行执行的事务,其执行结果必须与按某一顺序 串行执行的结果相一致。
隔离性 (Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
持久性 (Durability): 对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障
4 并发问题
1. 脏读
举例:订单 A 需要商品 A20 件,订单 B 需要商品 A10 件。仓库中有商品 A 库存是 20 件。订单 B 先查询,发现库存够,进行扣减。在扣减的过程中,订单 A 进行查询,发现库存只有 10 个不够订单数量,抛出异常。这时候订单 B 提交失败了。库存数量又变成 20 了。这时候,仓库人员去查库存,发现数量是 20,可是订单 A 却说库存不足,这就让人很奇怪。
2. 不可重复读
举例:库房管理员查询商品 A 的数量,读取结果是 20 件。这是订单 A 出库,扣减了商品 10 件。这时管理员再去查商品 A 时,发现商品 A 的数量时 10 件和第一此查询的结果不同了。
3. 幻读
举例:操作员查询可生产单量 10 个,调用接口下发 10 个订单,事务 A 增加 10 个订单。操作员获取 10 个订单落库,查询 发现变成 30 个订单。
5 事务隔离级别
一个事务可以读取到其他事务未提交的数据,会出现脏读,所以叫做 RU,它没有解决任何的问题。
一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的数据,它解决了脏读的问题,但是会出现不可重复读的问题。
它解决了不可重复读的问题,也就是在同一个事务里面多次读取同样的数据结果是一样的,但是在这个级别下,没有定义解决幻读的问题。
在这个隔离级别里面,所有的事务都是串行执行的,也就是对数据的操作需要排队,已经不存在事务的并发操作了,所以它解决了所有的问题。
6 解决数据读一致性
6.1 LBCC
6.2 MVCC
6.2.1 Undo log
新增一条记录的时候,在创建对应 undo 日志时,只需要把这条记录的主键值记录下来,如果要回滚插入操作,只需要根据对应的主键值对记录进行删除操作。
删除一条记录的时候,在创建对应 undo 日志时,需要把这条数据的所有内容都记录下来,如果要回滚删除语句,需要把记录的数据内容生产相应的 insert 语句,并插入到数据库中。
更新一条记录的时候,如果没有更新主键,在创建对应 undo 日志时,如果要回滚更新语句,需要把变更前的内容记录下来,如果要回滚更新语句,需要根据主键,把记录的数据更新回去。
更新一条记录的时候,如果有更新主键,在创建对应 undo 日志时,需要把数据的所有内容都记录下来,如果要回滚更新语句,先把变更后的数据删掉,再执行插入语句,把备份的数据插入到数据库中。
事务 ID:MySQL 维护一个全局变量,当需要为某个事务分配事务 ID 时,将该变量的值作为事务 id 分配给事务,然后将变量自增 1。
事务 A id 是 1 插入一条数据 X,这条数据的 trx_id =1 ,roll_pointer 是空(第一次插入)。
事务 B id 是 2 对这条数据进行了更新,这条数据的 trx_id =2 ,roll_pointer 指向 事务 A 的 undo log.
事务 C id 是 3 又对数据进行了更新操作,这条数据的 trx_id =3,roll_pointer 指向 事务 B 的 undo log.
6.2.2 Read-View 一致性视图
m_ids:表示在生成 ReadView 时当前系统中活跃的读写事务的事务 id 列表。
min_trx_id:表示在生成 ReadView 时当前系统中活跃的读写事务中最小的事务 id,也就是 m_ids 中的最小值。
max_trx_id:表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。
creator_trx_id:表示生成该 ReadView 的事务的事务 id。
如果被访问版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的 trx_id 属性值大于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
6.2.3 数据的查找方式
1. 快照读
2. 当前读
6.2.4 数据举例
事务 B id=2 进行了查询操作(MVCC 只读取创建时间小于当前事务 ID 的数据或者删除时间大于当前事务 ID 的行)
事务 B 的结果是 (商品 A:10, 商品 B:5)
事务 B id=2 进行了查询操作(MVCC 只读取创建时间小于当前事务 ID 的数据或者删除时间大于当前事务 ID 的行)
事务 B 的结果是 (商品 A:10, 商品 B:5)
事务 B id=2 进行了查询操作(MVCC 只读取创建时间小于当前事务 ID 的数据或者删除时间大于当前事务 ID 的行)
事务 B 的结果是 (商品 A:10, 商品 B:5)
事务 B id=2 进行了查询操作(MVCC 只读取创建时间小于当前事务 ID 的数据或者删除时间大于当前事务 ID 的行)
事务 B 的结果是 (商品 A:10, 商品 B:5)
6.2.5 可解决问题
7 小结
转自:OSCHINA
链接:https://my.oschina.net/u/4090830/blog/5580720
- EOF -
看完本文有收获?请转发分享给更多人
关注「ImportNew」,提升Java技能
点赞和在看就是最大的支持❤️