一 B+树逻辑特征:
1)m阶b+树要求每个节点最多有m个子节点
2)有k个子节点的非叶子节点拥有k-1个键,键按照升序排列
3)所有节点key从小多大排序,子节点之间指针关联,子节点都子同一层
4)所有的上一出层节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素
5)方便范围 主键查询
6)方便排序和分页
B+结构体
class Node {//非叶节点Node parent;Node[] childs;Object[] keys;}class LeafNode {//叶节点Node parent; Object values[]; LeafNode left; LeafNode right;}
二 B+树 聚集索引
1)数据库不是一条条读写,innodb最小的存储单元是页(page)是按页读取,一页16k大小
2)除了最低层页存储了记录外,其他层存储的是key值,数据页与页之间链表关联
3)查询从根节点查询,根据key找到对于的页,找到数据页,在已经排好序的页内部查询,
树的高度2到3层,最多3次查询页数据就查询出来记录
4)每个表都有一个聚集索引,索引就是数据,数据就是索引
三 B+树辅助索引
1)辅助索引和聚集索引结构类似,区别在于辅助索引的叶子节点的页不存储数据行,而是存的主键值,所以在辅助索引查询时,会查询2课B+树,先从辅助索引查询主键,在从聚集索引查询最终的记录。
四 数据库的锁
按照锁的粒度可以有 锁表,锁行,锁住一个范围(gap)
按照锁的模式有:共享,排他,意向
1)共享锁 S lock 允许事务都取一行数据
2)排他锁 X lock 允许事务删除或者更新一行数据
3)意向共享锁 IS
4)意向排他锁 IX
意向锁的作用
意向锁锁的粒度是表级别的锁,行排他锁 和表排他锁锁是不兼容的,如果事务A加了某行 行排他锁,事务B 要加一个表级别排他锁,事务B 怎么判断有了行锁呢,它需要遍历表中所有行记录判断有没有行排他锁,这种判断效率太低,那么可以通过意先锁来处理,事务A 加排他行锁时,会在表级别上加意向排他锁,事务B在加表排他锁时,直接查看有没有在表上的意向锁,如果有的话,就阻塞。
通过意向锁来串起两个不同粒度(表 行)的锁之间如何做互斥判断
5)间隙锁(gap lock)临建锁(next-key lock)
案例:
建立表t,插入主键值1,2,3,4,7,8, 6条记录,在事务为可重复读的情况下开启两个会话,会话A执行
Select* from t where a<6 lock in share mode 在会话A 没有提交事务释放锁的时候 ,开启会话B执行
Insert into t 5,那么会话B会阻塞,事务A 有一个next-key lock 锁定(负无穷到6),如果插入的不是这个范围的是可以的。在事务隔离级别是 可重复读的模式下,就是加了范围锁,保证事务可重复读。
五 一致性非锁定读,多版本控制(MVCC)
非锁定读是没有阻塞的,读取不会占用和等待表上的锁,它读取是是行记录版本快照
,在读已提交的事务级别,一致性非锁定读是被锁定行的最新的一份快照
在可重复读的事务级别,一致性非锁定读 读取的是事务开始时候的数据版本
示例:
事务A读已提交
事务A可重复读情况
Mvcc原理
1) DATA_TRX_ID:最近更新这条记录的Transaction ID,数据库每开启一个事务,事务ID都会增加,每个事务拿到的ID都不一样
2) DATA_ROLL_PTR:用来存储指向Undo Log中旧版本数据指针,支持了事务的回滚
事务读已提交情况:事务A 插入数据Insert into table value 1,2018-4-28
当前读的事务ID为read_id,记录当前存储的事务ID为tid,当前系统中未提交的事务中(Read_View中)的最大最小事务ID分别为max_tid和min_tid,那么数据时间点3情况
read_id=3,tid=2,Read_view(2,3)
时间点5
read_id=3,tid=2,Read_view(3,3)
如果事务是可重复读 Read_view(2,3)
六 读加锁 select ...for update 和select .. lock in share mode
除串行select语句都是非一致性锁定读,如果需要读加锁 可以用for uodate 和lock in shard mode 语句。
1)for update 读取的行 加排他锁。
2)Lock in shard mode 读取的记录加共享锁。
事务没有提交锁是不会释放的。
七 事务并发导致的问题
八 事务隔离级别
怎么解决更新丢失问题?
案例:商品(product)库存量 (Stock)并发扣减问题
减库存代码逻辑
Select stock from product where id =1If(stock==0){ 扣减失败,提示库存不足}Update product set stock-1 where id =1
多线程并发访问 会出现库存为-1的情况,原因事务 AB 同时读取到库存为1,查询和共享操作没有原子性。
解决方法
1利用单个sql语句的原子性
Update product set stock-1 where id =1 and stock >0
2 悲观锁
Select stock from product where id =1 for updateIf(stock==0){ 扣减失败,提示库存不足}Update product set stock-1 where id =1
3乐观锁 CAS思想
While(!result){ Select stock,version from product where id =1 If(stock==0){ 扣减失败,提示库存不足 } result =update product set stock-1 ,version=version+1 where id =1 and version =version}
4 单机synchronized,注意锁的粒度是在当前的商品,分布式锁