封锁
S 锁/X 锁
SQL 中规定了两种基本锁 S 锁和 X 锁,对数据元素加 S 锁之后所有事务只能再对数据元素加 S 锁而不能加其他锁;对数据元素加 X 锁后仅加锁事务可对该元素加锁。具体对于锁的实现会考虑到很多复杂因素,例如 MySQL 拥有多种类型的锁,特征是命名上会用 Shared 和 Exclusive 来区分具体是 S / X 锁。
MySQL
全局锁 flush tables with read lock;
表锁
lock tables xxx write(read) 手动上锁
MDL 防止 DDL 和 DML 冲突
IS/IX select from lock in share mode; 解决不同等级的锁的优先级的问题
行级锁
行锁
间隙锁
临键锁
next-key lock [now, +∞)
四大并发问题
四大问题分别为:
丢失修改(Lost Update):事务 A B 同时希望修改数据元组 a ,由于并发错误的按照下列顺序执行:
ARead(a), BRead(a), AWrite(a), BWrite(a), Acommitted, Bcommitted,这时 B 参照了 A 未修改过的版本进行了修改,A 的修改丢失了!
脏读(Dirty Read):事务 A 希望修改数据元组 a 事务 B 希望读取数据元组 a,顺序如下:
[ARead(a), AWrite(a)], BRead(a), AExceptionHappen!!!, ARollback, Bcommitted,此时 B 读取到的是 A 修改失败的版本,事实上并不存在于数据库中。
不可重复读(Unrepeatable Read):事务 A 希望读取数据元组 a 并加以验算,事务 B 希望对数据元组 a 进行修改,顺序如下:ARead(a), [BRead(a), BWrite(a)], BComitted, ARead(a), ACommitted,由于 B 的修改,A 两次读取到的数据不同。
幻读(Phantom Read):事务 A 希望统计关系表 T 数据元组数,事务 B 希望向关系表 T 中插入一些数据:
ACount(T), BInsert(B), BCommitted, ACount(T), ACommitted, 由于 B 的插入,A 两次计数并不相同。
SQL 标准中分别对应四种解决方案,Read Uncommitted, Read Committed, Repeatable Read, Serial Read,
分别是:对 update 加 X 锁作用至 committed;+ 对 select 加 S 锁,语句结束后释放;延长 S 锁至 committed
而在 MySQL 中的 Innodb 对应的解决方案主要依赖 MVCC :默认的 update 会加行级锁 X;每次均生成 ReadView; 仅第一次生成 ReadView 后续复用;不使用 MVCC 而是依赖读锁和写锁至 commit
MVCC
根据每行的隐藏字段(事务 id,回滚指针),Undo log(行版本) , ReadView { m_ids min_trx_id, max_trx_id, creator_trx_id } 来实现版本链的追踪,找到合适版本,主要需要找到小于 min_trx_id 或
者不在 m_ids 中的版本