MySQL之技术内幕
- 1.MVCC模式
- 2. 实现mvcc模式的基础点
- 3.MySQL锁的类型
- 4. 谈谈分库分表
- 5. 分表后的id咋么保证唯一性呢?
- 6. 分表后非sharding key的查询咋么处理的?
1.MVCC模式
MVCC, 是multi-version concurrency control的缩写,即多版本并发控制,是一种并发控制的方法,在数据库管理系统中,实现对数据库的并发访问技术,对应编程中的事务内存。
MySQL的Innodb引擎中在隔离级别(读已提交和重复读)下的事务,对于select查询操作会访问版本链
中的记录的过程。
这就使得别的事务可以修改记录,反正每次修改都会在版本链中记录。SELECT可以去版本链中查找,这就实现了读-写,写-写,的并发执行,提高了系统的性能。
2. 实现mvcc模式的基础点
- 隐式字段:
如上图,db_row_id是数据库默认为该行记录生成的唯一隐式主键;db_trx_id是当前操作该行记录的事务id;而db_roll_ptr是一个回滚指针,用于配合undo log日志,指向上一个旧版本,;delete flag没有展示出来。 - undo log
从上图看,不同的事务或者同一事务对同一记录修改,会导致该记录的undo log成为一条记录版本线性表,即版本链,undo log的链首就是最新的旧记录,链尾就是最早的记录。 - ReadView
读已提交和可重复读的区别就在于它们生成ReadView的策略不同。
ReadView有个列表存储了当前系统活跃的读写事务,也就是当前begin了但没有提交的事务。通过这个列表来判断记录的某个版本是否对当前的事务可见。-
假设当前列表里的事务id为[80,100]:
- 如果你要访问的记录版本的事务id为50,比当前列表最小的id80小,那说明这个事务在之前就提交了,所以对当前活动的事务来说是可访问的。
- 如果你要访问的记录版本的事务id为90,发现此事务在列表id最大值和最小值之间,那就再判断一下是否在列表内,如果在那就说明此事务还未提交,所以版本不能被访问。如果不在那说明事务已经提交,所以版本可以被访问。
- 如果你要访问的记录版本的事务id为110,那比事务列表最大id100都大,那说明这个版本是在ReadView生成之后才发生的,所以不能被访问。
-
这些记录都是去undo log 链里面找的,先找最近记录,如果最近这一条记录事务id不符合条件,不可见的话,再去找上一个版本再比较当前事务的id和这个版本事务id看能不能访问,以此类推直到返回可见的版本或者结束。
-
3.MySQL锁的类型
说两个维度:
- 共享锁和排他锁
- 读锁是共享的,可以通过lock in share mode实现,这时候只能读不能写。
- 写锁是排他的,它会阻塞其他的写锁和读锁。从颗粒度来区分,可以分为表锁和行锁两种。
- 表锁和行锁
- 表锁会锁住整张表并且阻塞其他用户对表的所有读写操作,比如alter修改表结构的时候会锁表。
- 行锁分为乐观锁和悲观锁:
- 悲观锁可以通过for update实现。
- 乐观锁可以通过版本号实现。
两个维度结合来看:
- 共享锁(行锁):shared locks
- 读锁(s锁):多个事务对于同一数据可以共享访问,不能操作修改使用方法: 加锁:SELECT * FROM table WHERE id=1 LOCK IN SHARE MODE
- 释锁:COMMIT/ROLLBACK
- 排他锁(行锁):Exclusive Locks
- 1写锁(X锁),互斥锁/独占锁,事务获取了一个数据的X锁,其他事务就不能再获取该行的读锁和写锁(S锁、X锁),只有获取了该排他锁的事务是可以对数据行进行读取和修改
- 使用方法:
DELETE/ UPDATE/ INSERT – 加锁
SELECT * FROM table WHERE … FOR UPDATE – 加锁
COMMIT/ROLLBACK – 释锁
- 意向共享锁(IS):
- 一个数据行加共享锁前必须先取得该表的IS锁,意向共享锁之间是可以相互兼容的
- 意向排它锁(IX): 一个数据行加排他锁前必须先取得该表的IX锁,意向排它锁之间是可以相互兼容的
- 意向锁(IS、IX)是InnoDB引擎操作数据之前自动加的,不需要用户干预;
- 意义: 当事务操作需要锁表时,只需判断意向锁是否存在,存在时则可快速返回该表不能启用表锁
- 意向共享锁(IS锁)(表锁):Intention Shared Locks 表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁 前必须先取得该表的IS锁。
- 意向排它锁(IX锁)(表锁):Intention Exclusive Locks 表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他 锁前必须先取得该表的IX锁.
4. 谈谈分库分表
- 垂直分库
基于现在微服务来说,都已经做到了按照业务垂直分库了。 - 垂直分表
垂直切分是将一张表切分为多个表,通常是按照列的关系密集程度进行切分,也可以垂直切分将经常使用到的列和不经常使用到的列切分到不同的表中。 - 水平分表
首先根据业务场景决定使用什么字段作为分表字段(sharding_key),比如我们现在日订单1000万,我们大部分的场景来源于C端,我们可以用user_id作为sharding_key,数据查询支持到最近3个月的订单,超过3个月的做归档处理,那么3个月的数据量就是9亿,可以分1024张表,那么每张表的数据大概就在100万左右。
比如用户id为100,那我们都经过hash(100),然后对1024取模,就可以落到对应的表上了。
5. 分表后的id咋么保证唯一性呢?
因为我们主键默认都是自增的,那么分表之后的主键在不同表就肯定会有冲突了。有几个办法考虑:
- 设定步长,比如1-1024张表我们分别设定1-1024的基础步长,这样主键落到不同的表就不会冲突了。
- 分布式ID,自己实现一套分布式ID生成算法或者使用开源的比如雪花算法这种
- 分表后不使用主键作为查询依据,而是每张表单独新增一个字段作为唯一主键使用,比如订单表订单号是唯一的,不管最终落在哪张表都基于订单号作为查询依据,更新也一样。
6. 分表后非sharding key的查询咋么处理的?
- 可以做成一个mapping表,比如这时候商家要查询订单列表咋么办?不带user_id查询的话你总不能扫全表吧?所以我们做一个映射表,保存商家和用户的关系,查询的时候先通过商家查询用户列表,在通过user_id去查询。
- 大宽表,一般而言,商户端对数据实时性要求并不是很高,比如查询订单列表,可以将订单表同步到离线(实时)数仓,再基于数仓去做成一张宽表,在基于其他如es提供查询服务。
- 数据量不是很大的话,比如后台的一些查询之后的,也可以通过多线程扫表,然后再聚合结果的方式来做。或异步的形式也是可以的。