数据库事务

之前写过数据库并发机制和事务的隔离级别 事务隔离级别在数据库的具体实现原理记录过数据的特性和隔离级别等,但是当时保留了一个问题,就是丢失更新,

如果两个线程先读取, 经过计算后, 对数据进行的并发修改, 如何避免后发线程对先发线程的覆盖;

MySQL中的事务原理和锁机制

避免丢失更新

原子写:

UPDATE counter SET value = value + 1 WHERE id = 'whatever';

for update显式锁定:

BEGIN TRANSACTION;
# for update就是为这行数据加了锁,提交或回滚后释放
SELECT * FROM users WHERE id = 'Eddie' FOR UPDATE;
# 拿到数据后,应用程序做校验,然后...
UPDATE users SET money = '99999999' WHERE id = 'Eddie';
COMMIT;

比较并设置(CAS, Compare And Set):

UPDATE wiki_pages SET content = '新内容' WHERE id = '007' AND content = '旧内容';

示例:

在订单库存场景中可以使用一下语句, 结合了原子写和 cas两种方式

update storeT set count = count-1 where sku_id = 1 and count > 1 
#校验库存数量大于扣减商品数量

补充:

快照读 和 当前读

根据数据库的类型和 隔离级别设置 快照读和当前读的实现方式有区别;

select 语句属于快照读;

u d i select..lock in share mode select for update语句 属于当前读 , 不会读取快照中的过期数据;

读提交隔离级别下,read view 是在每个SQL语句开始执行的时候创建的,在这个隔离级别下,事务在每次查询开始时都会生成一个独立的ReadView, 所以会产生不可重复读.

可重复读,在第一次读取数据时生成一个ReadView,对于使用REPEATABLE READ隔离级别的事务来说,只会在第一次执行查询语句时生成一个ReadView,之后的查询就不会重复生成了,所以一个事务的查询结果每次都是一样的。当然就会存在数据过期的情况。

想了解下spring的事务控制中 如何和读写锁产生关系的。

自己的理解 , 事务和锁没关系, 事务的隔离级别是通过不同锁或则mvcc 日志等配合实现的,

在同一service方法的事务中 ,读取数据只会拿到读锁(隔离级别有关), 只有修改数据时 才会拿到写锁 ,

不同隔离级别下加锁情况

数据库四大特性的实现

image-20240924111822118

总结:

  • 数据库的并发问题通过设置隔离级别, 隔离级别通过 锁机制 mvcc 实现;
  • mysql的默认隔离级别是rr 通过锁和mvcc 实现, 可以预防 脏读, 不可重复读 间隙锁防止幻读;
  • 默认的锁级别 行锁, 可以通过设置修改,
  • where条件中使用索引 加行锁, 不适用索引的 加表锁.
  • 意向锁 是数据库处理的不需要手动加
  • 对于 u d i innodb会自动给设计数据集添加排它锁.
  • 对于select语句默认不加锁.,可以手动加 for update , select..lock in share mode 等;
  • myisam 不支持事务 只支持表锁