事务是用户定义的一系列SQL操作的集合,这些操作要么都做,要么都不做,事务不会出现中间状态。
事务SQL操作
开启:begin transaction
提交:commit
回滚:rollback
ACID特性
原子性:对应undolog的记录,如果一个事务提交失败,那么对数据的修改便会通过undolog回滚。
一致性:指事务执行前后数据库的完整性约束没有被破坏。如唯一约束、非空、自增约束等等。
隔离性:事务之间的操作不会互相影响。
持久性:对应redolog的记录,redolog记录了事务提交后对数据页的修改。此时采用redolog和binlog的两阶段提交,就算宕机redolog中也有记录。
redolog同时起到性能作用,MySQL会定时的将redolog的记录刷到磁盘,这样就避免了一提交一刷盘导致性能下降。
隔离级别
RU 读未提交:读不做处理;写加X锁
RC 读已提交:每次读都会使用mvcc,保证读取的是最新提交;写加X锁
RR 可重复读:事务开始时使用mvcc,直到事务提交;写加X锁
Serializable 可串行化:读加S锁;写加X锁
脏读、不可重复读、幻读
脏读:指一个事务读取到另一个事务未提交的数据,如果另一个事务回滚,那么该数据易导致业务出错。
不可重复读:指事务在执行过程中,多次读取同一个数据,数据不一致
幻读:在进行范围读取时,多次读取的统一范围,结果集不一样
MVCC(Multi-Version Concurrency Control (多版本并发控制)
read view:
m_ids:创建read view时,已启动但未提交的事务id列表
min_trx_id:创建read view时,已启动但未提交的最小事务id
max_trx_id:创建read view时,预分配给下一个未开启事务的id
creator_trx_id:创建该read view的事务id
聚集索引的隐藏列
trx_id:修改数据时的事务id
roll_pointer:指向redolog中的历史数据
事务可见性
一个事务对数据是否可见,在mvcc机制中,是通过判断事务id在read view中的关系决定的
trx_id < min_trx_id 表示该数据在当前事务启动时已提交,可见
trx_id >= max_trx_id 表示该数据在当前事务启动后的提交,不可见
min_trx_id <= trx_id < max_trx_id :当修改数据的事务,在当前事务启动时处于“已启动但未提交”状态。如果trx_id在m_ids中,则未提交不可见。如果trx_id不在m_ids中,则已提交,可见。(当一个事务提交后,会自动从m_ids中移除)
读已提交:每次事务开始时,创建一个新的read view
可重复读:事务在开始到提交的过程中,只使用一份read view
锁知识和相关命令
全局锁
flush tables with read lock 全表仅可读、unlock tables 全表解锁
用于数据备份
表级锁
表锁:lock tables 'table'[read/write]
元数据锁:crud 、alter
意向锁:快速判断表里是否有记录加锁
行级锁
记录锁:S 共享锁、X 排他锁
间隙锁:RR,RC。查询数据防止其他事务在记录间插入新的记录,从而避免幻读。
临键锁:对数据两边的间隙加锁,左开右闭,next-key Lock(行锁+间隙锁)
如何解决RR隔离级别下,出现的幻读问题?
问题场景:通过mvcc的快照度,可以解决在读取范围数据不出现幻读,但是没读到不代表不能操作。假设一张表有一个id为5的数据,事务A读取id小于10,可以读到id为5的数据,此时事务B立马插入一条id为3的数据并提交,因为mvcc事务A仍然只能读到id为5的数据,但此时可以操作id为3的数据。
解决办法:加间隙锁 for update。通过加上间隙锁,上面的场景中,事务B在插入数据时会因为等待事务A的间隙锁而阻塞。
死锁
原因:并发事务在执行过程中,因争夺锁资源而造成互相等待。
加锁顺序导致死锁:不同表加锁顺序相反、相同表不同行加锁顺序相反
解决办法:调整加锁顺序
锁冲突死锁:RR级别下,插入意向锁与gap锁冲突。A事务加上gap锁,并获取插入意向锁防止其他事务插入数据,B事务对gap锁住的范围执行DML操作,事务B获取行锁前尝试获取对应行的插入意向锁。在这种情况下,事务A持有了 Gap锁并等待事务B释放行级锁,而事务B持有了行级锁并等待事务A释放插入意向锁。
解决办法:降级到RC,通常就没有gap锁了
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂