数据库四大特性
1、原子性(Atomicity)
事务是数据库的逻辑工作单位,事务中包括的诸操作要么全做,要么全不做。
2、一致性(Consistency)
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
3、隔离性(Isolation)
一个事务的执行不能被其他事务干扰。
4、持续性/永久性(Durability)
一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
事务隔离级别以及各级别下的并发访问问题
更新丢失 即一个事务的更新覆盖了另一个事务的更新
但其实InnoDB已经避免了这种事情的发生,所以体验的话,可以尝试其他的存储引擎。
脏读
脏读,也就是说,读取了另一个更新事务,更新之前的数据,也就是,“读未提交”
READ-COMMITTED即已提交读,事务隔离级别及以上可避免。 RC
使用select @@tx_isolation查看mysql的默认事务隔离级别
脏读例子Demo
set session transaction isolation level read uncommited
start transaction
update tableA set columnA = 10-1 where id = 1;
select columnA from tableA where id = 1
这时应该查出来columnA = 9
但是不提交事务,仅仅进行更新
第二个事务
我们直接使用select columnA from tableA where id = 1
查出来也理应是9,这是肯定没有问题的,但是我们第一个事务并没有对事务进行操作,这也就是所谓的"读未提交"。
如果我们针对第一个事务使用rollback,那么第二个必定脏读,而如果第一个事务使用commit,就没事了。
而我们如何避免“读未提交”呢,那就是提升隔离级别为read committed及以上,就可以了。
不可重复读
不可重复读——REPEATABLE-READ事务隔离级别及以上可以避免。 RR
意思也就是说,事务A多次读取某一个字段,在这个过程中,事务B对这个字段的数值进行了更改,导致读取出现了错误。
要重现这个问题可以把隔离级别设为最低
set session transaction isolation level read uncommitted
而当我们将事务的隔离级别设置成repeatable read的话(set session transaction isolation level repeatable read )就不会出现这样的问题
幻读
幻读,可以用SERIALIZABLE的事务隔离级别避免。意思是说,事务A进行了查询全量操作,而事务B进行了删除/插入表操作,这时候A的查询结果就会像产生幻觉一样(多操作一条或者若干条)
Select * from tableA lock in share mode
当前读和快照读
当前读
select…lock in share mode (共享读锁)
select…for update
update , delete , insert
当前读, 读取的是最新版本, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题。
例如,假设要update一条记录,但是另一个事务已经delete这条数据并且commit了,如果不加锁就会产生冲突。所以update的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。
快照读
单纯的select操作,不包括上述 select … lock in share mode, select … for update。
Read Committed隔离级别:每次select都生成一个快照读。
Read Repeatable隔离级别:开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读。
RC, RR级别下的InnoDB的非阻塞读如何实现
数据库有着三个隐藏的字段,通过这三个字段,通过这些字段我们实现了记录追踪,历史回溯
数据行里的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID字段
undo日志(每操作一次数据,顺序增加一个日志)
read view(快照本照了)
数据行里的字段
DB_TRX_ID 最后一次操作事务ID
DB_ROLL_PTR 回滚指针
DB_ROW_ID InnoDB 表中在没有默认主键的情况下会生成一个6字节空间的自动增长主键
undo日志
第一次修改数据12->32
第二次修改数据13->43
我们看到日志被记录下来,如果 我们第二次事务回滚了,是找到回滚指针指向的undo日志,再回滚操作
read view
RR下,事务读取数据的时机非常重要,第一次读取后数据会创建快照,以后会读取快照,事务提交前,再读取都是读取第一次读取的数据
RC下,每一次数据读取,都会创建一个新的快照,所以RC能读取到别人提交的结果
当前读的实现方式:next-key锁(行记录锁+Gap间隙锁)
间隙锁:只有在Read Repeatable、Serializable隔离级别才有,就是锁定范围空间的数据,假设id有3,4,5,锁定id>3的数据,是指的4,5及后面的数字都会被锁定,因为此时如果不锁定没有的数据,例如当加入了新的数据id=6,就会出现幻读,间隙锁避免了幻读。
- 对主键或唯一索引,如果当前读时,where条件全部精确命中(=或者in),这种场景本身就不会出现幻读,所以只会加行记录锁。
- 没有索引的列,当前读操作时,会加全表gap锁,生产环境要注意。
- 非唯一索引列,如果where条件部分命中(>、<、like等)或者全未命中,则会加附近Gap间隙锁。例如,某表数据如下,非唯一索引2,6,9,9,11,15。如下语句要操作非唯一索引列9的数据,gap锁将会锁定的列是(6,11],该区间内无法插入数据。
快照读的实现方式:undo log和多版本并发控制MVCC
下图右侧绿色的是数据:一行数据记录,主键ID是10,name=‘Jack’,age=10, 被update更新set为name= ‘Tom’,age=23。
事务会先使用“排他锁”锁定该行,将该行当前的值复制到undo log中,然后再真正地修改当前行的值,最后填写事务的DB_TRX_ID,使用回滚指针DB_ROLL_PTR指向undo log中修改前的行DB_ROW_ID。
DB_TRX_ID: 6字节DB_TRX_ID字段,表示最后更新的事务id (update,delete,insert)。此外,删除在内部被视为更新,其中行中的特殊位被设置为将其标记为已软删除。
DB_ROLL_PTR: 7字节回滚指针,指向前一个版本的undo log记录,组成undo链表。如果更新了行,则撤消日志记录包含在更新行之前重建行内容所需的信息。
DB_ROW_ID: 6字节的DB_ROW_ID字段,包含一个随着新行插入而单调递增的行ID, 当由innodb自动产生聚集索引时,聚集索引会包括这个行ID的值,否则这个行ID不会出现在任何索引中。如果表中没有主键或合适的唯一索引, 也就是无法生成聚簇索引的时候, InnoDB会帮我们自动生成聚集索引, 聚簇索引会使用DB_ROW_ID的值来作为主键; 如果表中有主键或者合适的唯一索引, 那么聚簇索引中也就不会包含 DB_ROW_ID了 。
其它:insert undo log只在事务回滚时需要, 事务提交就可以删掉了。update undo log包括update 和 delete , 回滚和快照读 都需要。