开发项目时遇到的横向越权、行锁表锁与事务的关联与区别、超卖问题
横向越权
横向越权指的是 用户A存储了相应信息在数据库, 假设此时用户B获得了用户A的主键(或者其他可查询行数据的信息),在我们不加以防护的情况下,用户B对A存储的对应内容进行修改,这就是横向越权。
在本章节对横向越权进行了防护,防护规则也很简单,就是进行鉴权。
通过对ThreadLocal中的用户信息与数据库中创建者的对应信息进行比对,从而鉴权。这样的鉴权,使得相应的修改操作,只能由创建者本人完成。
mysql数据库的排他锁
数据库的单条增删改是并发安全的,这是由于innodb底层实现的排他锁决定的。innodb引擎保证了在进行增删改时其他线程拿不到改行数据。
也就是说排他锁是一个行级锁。
那么既然mysql有了行级锁,为什么还需要使用事务?
原因很简单,单条语句的并发安全不代表多条语句的执行是并发安全的,事务的出现就是为了保证多条sql语句的原子性,从而实现多条语句的逻辑无差错。
为什么有了排他锁还会在电商系统中出现超卖?
电商系统中,多个并发线程来对数据库进行读写,如果此时读写是分离的状态,那么此时会出现超卖。
因为排他锁并不会对读上锁,而只会对增删改进行上锁。
那么如果业务逻辑为:
假设初始 stock = 1
,有三条并发请求(线程 A、B、C):
sql复制编辑
-- 线程 A
SELECT stock FROM product WHERE id=100; -- 读到 1-- 线程 B
SELECT stock FROM product WHERE id=100; -- 也读到 1-- 线程 C
SELECT stock FROM product WHERE id=100; -- 也读到 1-- 接着它们分别判断 stock>0,都成立
-- 然后各自执行:
UPDATE product SET stock = stock - 1 WHERE id=100;
所以,导致电商超卖的原因实质上是事务的不一致性,在读写分离时需要添加事务,或者使用同步读写、乐观锁来进行解决。
比如将上述sql语句修改为读写同步:
UPDATE productSET stock = stock - 1WHERE id = 100AND stock > 0;
这样一条sql语句实质上是具备排他锁的,所以不会出现并发安全问题。
为什么innodb已经有了行级锁,还需要表锁?
行级锁与表锁并不矛盾,在并发性高的表中,需要使用到行级锁,因为颗粒更细,能够提升整体表的读写效率。但是由于行锁的开销更高: 需要管理锁链、索引定位、MVCC 等 ,所以尽量在并发高的热点表中进行手动添加。
那么显然由于行锁的消耗高,所以表锁对于并发量不高,但又需要保证并发安全的表,就可以使用表锁,表锁的颗粒更大,但是表锁是一个轻量级锁,开销更小。
除此之外,表锁在DDL中具有必要性,因为DDL语言对表的结构进行修改,此时需要对整个表进行上锁,避免出现脏数据。
什么是乐观锁,乐观锁为什么能够解决超卖问题?
首先需要明确,乐观锁可以是锁,也可以是一种思想。
乐观锁的思想是,仅在非读操作的情况下进行检测加锁。也就是
” 先不加锁,假设不会发生冲突;在写入前检测是否有冲突,若检测到冲突则重试或报错 “
对应的悲观锁思想就是在操作之前进行上锁。
比如在解决超卖问题,读写分离时可以新建一个version列:
进行读:读出当前version
进行写:判断写时的version是否是读时的version,如果不是,则重试或者报错。
刚才的sql语句进行改写,就能得到乐观锁对应的语句:
读:SELECT stockFROM productWHERE id = 100;写:UPDATE productSET stock = stock - 1WHERE id = 100AND stock = #{oldStock};