知识点六:隔离级别,表锁,行锁,死锁,更新丢失,索引

Mysql 隔离级别

  1. 读未提交,读已提交,可重复读:事务过程中看到的数据和事务启动时看到的数据是一致的;串行化:读会加读锁,写会加写锁,读写锁,写写锁冲突时,后一个事务需要前一个事务执行完;

  2. 隔离级别的实现:

	读未提交:每次返回记录上的最新值;
	读已提交:MVCC多版本视图,在sql 执行前开始创建视图;
	可重复读:MVCC 多版本视图,在事务启动时创建视图;
	串行化:加锁的方式规范并行访问;
  1. MVCC:是指每条记录在更新时都会同时记录一条回滚操作,通过回滚段就可以生成同一条记录的多个版本,每个版本和更新的 transaction_id 绑定,transation_id 是严格递增的,这样开启事务时,通过一个数组记录当前时刻启动了但未提交的 transactionId,那么每条记录的可见性,就通过比较是否小于该数组记录来判断,由此就生成了 mvcc 的视图;
	mvcc 分析可见性,通过:1. 自己的更新总是可见;2. 版本未提交,不可见;3. 版本已提交,在视图创建后提交的,不可见;4. 版本已提交,而且是在视图创建前提交的,可见;
  1. 长事务需要记录大量的回滚段,且占用锁资源时间长,要尽量避免;

  2. 建议生产中开启自动提交,当需要手动定义一个事务边界时,使用 begin-commit/rollback;

全局锁和表锁:

  1. 全局锁就是对整个数据库实例加锁,主要用在数据库备份的场景中,加锁后整个数据库实例处于只读状态;

  2. 表锁是限制对单表的读写操作,语法是 lock tables table_name read/write,用在不支持行锁的存储引擎上控制并发;

  3. 另一个表级锁是 MDL,当对一个表做增删改查操作时,会加 MDL 读锁;当对表结构做变更操作时,会加 MDL 写锁;并且是系统默认加锁,且读锁和读锁不冲突,其他锁之间是相互冲突的;

  4. 要特别注意,给表加字段,修改字段,加索引,都是需要扫描全表的,并且加 MDL 写锁,会阻塞后面的所有增删改查操作请求,如果加 mdl 写锁也需要阻塞等待之前线程释放 mdl 读锁,所以可以在sql 中添加 wait n,nowait 来限制等待 mdl 写锁时间,避免长时间阻塞后续操作;

行锁

  1. innodb 引擎支持实现,用来控制并发;

  2. 两阶段锁协议,事务中,行锁是在需要的时候才加上,要等到事务结束时才释放;因此事务中,要把最有可能冲突的记录尽量放在事务后半段执行,这样可以减少锁时间;

死锁

  1. 死锁,事务A update id =1,事务B update id = 2,事务B update id = 1,事务A udpate id = 2,此时两个事务就发生了死锁,解决方案,一种是设置锁等待超时,太长,如果发生死锁,业务需要阻塞长时间,太短,会将正常的锁等待当作死锁处理;

  2. 另一种是死锁检测,每一个等待锁的线程,都判断是否因为自己的加入造成了死锁,需要判断更新同一记录的所有线程,如果发现死锁,主动回滚自己的事务,推荐使用这种策略,因为第一种锁超时策略的超时时间难把握;

  3. 死锁检测有一定成本,特别对于并发竞争激烈的数据库行记录,因为更新操作都需要加锁,需要阻塞进行,并且死锁检测的成本会急剧增加,此时可以考虑将单行竞争记录的行记录,分为多行来记录,减少单行的并发修改操作;

当前读和更新丢失

  1. update 操作,既需要加锁,同时都是先读后写的,并且是当前读,只能读当前的值,这样就不会发生更新丢失,除非业务代码中是先查询,然后根据查询结果来更新的,因为这个查询就不是当前读了,只会读到 mvcc 视图中的值;

  2. 普通的 select 语句是一致性读,根据 mvcc 视图来确定可见性,如果 select 语句加上 lock in share mode 或 for update,就是是当前读,能读到最新的值;

  3. 回滚丢失,任何隔离级别都不会出现;

  4. 示例参考:https://time.geekbang.org/column/article/70562?utm_source=pinpaizhuanqu&utm_medium=geektime&utm_campaign=guanwang&utm_term=guanwang&utm_content=0511;

数据库索引结构

  1. 哈希索引,适用于只有等值查询,不适合范围查找,因为无序存放,需要将范围区间全部扫描一遍;

  2. 有序数组索引,等值查找,使用二分查找就可以,范围查找,也只需要先找到大于等于区间的最小值,然后依次遍历,直到第一个大于区间最大值的即可,但这种结构索引的修改成本很高,因此适合静态数据;

  3. 二叉平衡树,查找和插入的时间复杂度都是 O(logN),树高 20,可以存储的节点是 100 万,通常每一层放入一个数据块中,那么一次查询可能就需要 20 次访问数据块,假设每次 io 访问数据块 10 ms,那么就需要 200ms,相当耗时;

  4. n 叉平衡树,假设 n = 1200 时,树高 3,就可以存储 17 亿的记录了,那么最多三次 io 操作访问数据块,就可以查找到索引值,比较适合做数据库索引结构;

  5. n 阶 b+ 树和 b 树,表示每个节点最多有 n -1 个关键字,n 个孩子节点,插入新的关键字时,如果节点关键字大于 n -1,就需要进行分裂,提取中间节点到父节点,b 树就是普通的 n 阶多叉树;

  6. b+ 树,每个父节点都会在其右子节点冗余存储一份,所以树中所有关键字在叶子节点都会冗余存储,其非叶子节点就只会存储索引值,叶子节点会存储索引值和主键值,并且每个叶子节点都有相邻叶子节点的指针,通过指针,叶子节点就是按照关键字从小到大顺序连接的;

  7. b+ 树这种结构,非叶子节点存储数据更多,每次查找都需要找到叶子节点,查询稳定性更高,并且叶子节点形成有序结构,更利于范围查找;

  8. innodb 引擎中,使用的是 b+ 树的索引结构,表中数据是按主键的索引结构存放的,每一个索引都是一棵 b+ 树,其中主键索引,聚簇索引,其叶子节点是整行数据,非主键索引,叶子节点是主键值,所以非主键索引查找,通常需要回表;

	主键通常选择自增,因为自增有序,很方便的维护索引树,如果无序的话,可能涉及数据页的分裂,合并,并且自增主键使得非主键索引的占用空间最小;
  1. 索引范围查找,是先找到区间最小值的索引值,然后按顺序遍历,直到找到第一个不满足范围区间的值即可;

  2. 使用联合索引是一个常用的性能优化手段,索引中包含所有要查询的字段列,联合索引有最左匹配的原则,有了 (a,b) 的联合索引,通常就不需要单独的 a 索引了;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章