mysql

123 阅读10分钟

一.存储结构

树:二叉树--平衡二叉树--红黑树--B树--B+树

B树:每个节点:key 主键 指针 子节点的地址,范围信息, 数据 存储的行记录 B树每个节点可以存储多个元素,相对于完全平衡二叉树整体的树高降低了,磁盘IO效率提高了。

image.png B+树:叶子节点存储数据,非叶子节点只存储指针,叶子节点之间为链表结构,B+树是B树的升级版,只是把非叶子节点冗余一下,这么做的好处是为了提高范围查找的效率。提高了的原因也无非是会有指针指向下一个节点的叶子节点。

image.png

数据库底层存储单元是页(一个磁盘块),每个磁盘块大小4K或8K,索引和数据都是以页为单位进行存储,因此每一页能存储更多的索引,相同高度的树就能够存储更多的数据,数据库从磁盘每次读取16KB

B+树在B树的基础上将data数据全部下移到叶子节点,所以非叶子节点空间能够存储更大的范围

回表:先通过普通索引的值定位聚簇索引值,再通过聚簇索引的值定位行记录数据,需要扫描两次索引B+树,它的性能较扫一遍索引树更低。
索引覆盖:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。
最左匹配:联合索引的使用,你在索引定义中以正确的顺序指定列,那么联合索引就可以加快对同一张表的多种不同类型的查询。
where条件中是按照顺序去匹配索引,如果条件不对应,索引不生效
例:id,name,age 联合索引(name,age)
where name = xx 用索引
where age = x 不用
where name = xx and age = xx 用索引
where age = xx and name = xx 用索引 ,因为sql优化
索引下推:在server做的过滤下推到存储层,联合索引中,where条件用到联合索引都在存储层先过滤出数据集,再在server层进行其他过滤排序

innoDB: 支持表锁和行锁
索引类型:B+树,自适应hash(由数据库决定是否使用hash索引,哈希表的特点就是可以快速的精确查询,但是不支持范围查询,所以适用范围是只有等值查询的场景,例如redis)
聚簇索引:数据和索引是存放在一起的,所以只能有一个聚簇索引,比如主键id
向innoDb插入数据时,必须包含一个索引,主键,没有主键可以是唯一键,都没有则是生成的一个6字节的rowid
两个数据文件:表结构,数据和索引
myisam:只支持表锁,不支持行锁,不支持事务
都是非聚簇索引, B+树 ,索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。
每张表对应三个文件,表结构,表数据,索引

二.事务

事务:数据库管理系统(DBMS)执行过程中的一个逻辑单位,有一个有限的数据库操作序列组成 支持事务的存储引擎 : innoDb,NDB

四大特性:A(Atomicity 原子性),C(Consistency 一致性) ,I(Isolation 隔离性),D(Durablity 持久性)

  • A(Atomicity 原子性):一个事务是不可分割的最小工作单元,整个事务要么全部成功,要么全部失败,不可能只执行中间的一部分操作。

  • C(Consistency 一致性):执行事务是使得数据库从一个一致性状态到另一个一致性状态,如果事务最终没有被提交,那么事务所做的修改也不会保存到数据库中。
    一致性是事务的最终目的,其他三个特性都是为了保证一致性服务的,原子性只能保证一部分一致性,在并发环境下需要依赖隔离性来保证一致

  • I(Isolation 隔离性):通常来说,一个事务提交之前对其他事务是不可见的,但是这里所说的不可见需要考虑隔离级别,比如未提交读在提交前对于其他事务来说也是可见的,隔离级别,在下面会详细讲。

  • D(Durablity 持久性):事务一旦被提交,那么对数据库的修改会被永久的保存,即使数据库崩溃修改后的数据也不会丢失。

事务并发的三大问题(读一致性问题):

脏读:一个事物读取到另一个事务未提交的数据导致在同一个事务中两次得到不一致的结果。
不可重复读:一个事物读取到另一个事务提交的更新或删除的数据,导致在同一个事务中两次得到不一致的结果。
幻读:一个事物读取到另一个事务提交的insert数据,导致在同一个事务中两次得到不一致的结果。

事务的隔离级别:
RU:Read Uncommitted (读未提交):最低的隔离级别
RC:Read Committed(读已提交):可以解决脏读      MVCC实现(当前查询时的快照)
RR:Repeatable Read(可重复读):解决脏读,不可重复读(innoDb中可以和间隙锁合作解决幻读) MVCC实现(第一次查询时的快照)
Serializable(串行化):解决所有并发问题(串行操作)

隔离级别的实现方式:
1.LBCC(Lock Based Concurrency Control):在读取数据前,对其加锁,阻止其他事务对其进行修改
2.MVCC(Multi Version Concurrency Control):多版本并发控制,生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取
解决读写冲突,通过回滚日志实现
实现原理:依赖于表的三个隐式字段,undo log,read view
隐式字段:
DB_TRX_ID :6byte,表示最近修改的事务id:记录创建这条记录或最后修改这条记录的事务id
DB_ROLL_PTR:7byte,回滚指针,记录这条记录的上一个版本
DB_ROW_ID :6byte,隐藏的自增id

-----mysql的事务日志:binlog。 这两种日志是属于InnoDB存储引擎的日志,和MySQL Server的Binlog不是一个维度的日志。 redo log ,undo log 回滚日志

redo log :修改数据实际是在内存中操作的,在一定的策略下才会回写到磁盘,进而减少io操作,而此时DB故障就会丢失数据,所以redo log 就是存储的当前最新的数据,在提交事务时,先把数据写到redolog 才会修改缓存中的数据
undo log:undo log主要记录了数据的逻辑变化,比如一条INSERT语句,对应一条DELETE的undo log,对于每个UPDATE语句,对应一条相反的UPDATE的undo log

read view 读视图:事务进行快照读的时候生成的数据库系统当前的快照,保证同一个事务中读到都是同一个版本的数据

ReadView中主要包含以下4个内容:

m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。等于最大事务id+1
creator_trx_id:表示生成该ReadView事务的事务id。由于只有在对表中记录做改动(增删改)时才会为事务分配事务id,所以在一个读取数据的事务中的事务id默认为0;

有了ReadView之后,我们可以基于以下步骤判断某个版本的记录是否对当前事务可见。

  • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

在MySQL中,READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同。READ COMMITTED在每次读取数据前都会生成一个ReadView,这样就能保证每次都能读到其它事务已提交的数据。REPEATABLE READ 只在第一次读取数据时生成一个ReadView,这样就能保证后续读取的结果完全一致。

  • SELECT InnoDB 会根据以下两个条件检查每条记录:
       只查找版本小于等于事务版本号的行
       只查找未定义删除时间或者删除时间大于事务版本号的行
  • INSERT InnoDB 为新插入的每一行保存当前的系统版本号作为行版本号
  • DELETE InnoDB 为删除的每一行保存当前的系统版本号作为行的删除版本号
  • UPDATE InnoDB 新增一条记录,保存当前系统版本号作为新增行的版本号 -在被删除记录的原始行,保存当前系统版本号作为被删除记录行的删除版本号

优点:

  • 因为有了两个隐藏列来记录数据的状态,所以大多数读操作都可以不加锁
  • 性能好,同时可以保证读取的数据是正确的

三.锁

3.1行锁:

上锁方式:
共享锁(S 锁)--读锁:多个读操作可以共享一把锁,都可以读取数据,但是无法修改,加锁方式 select * from xx where id = 1 LOCK IN SHARE MODE
排它锁(X锁)--写锁:排它锁不能与其他锁并存,如一个事务获取了一行数据的排它锁,其他事务就不能获取该行的锁(S,X锁)只有获取到排它锁的事务可以读取和修改
加锁方式:update、delete、insert 自动加锁,select。。。。FOR UPDATE;
行锁锁住的是索引列,没有索引时,是表锁

区间定义:

  • 记录Record:锁的列数据(例如 id 123)
  • 间隙Gap:两个数据之间的区间 n行记录,n+1个间隙(负无穷-1,1-3,3-正无穷,闭区间)
  • 临键Next-key:避开区间,包含下一个闭区间的开始记录((负无穷,1],(1,3],(3,正无穷))

行锁类型:

  • 记录锁Record LOCK:主键(唯一键)等值查询,精准匹配时,锁住该行记录
  • 间隙锁 Gap Lock:where条件中 索引 的值不存在,锁住向上和向下最近一条记录的区间,不允许insert,例如:id(1.4.7.10),查询id=6,锁住id>4,<=7的区间
  • 临键锁(包含了间隙锁,是记录锁和间隙锁的组合): 查询id>20,锁住>10的区间,插入11会等待锁释放,查询id>5,and id<9 ,锁住d>4,<=10, innoDb通过间隙锁实现不会发生幻读

3.2表锁:

意向(共享)锁(IS锁):由数据库引擎自己维护的,无法手动创建,事务给行数据加共享锁,会先取得该表的IS锁
意向(排他)锁(IX锁):由数据库引擎自己维护的,无法手动创建,事务给行数据加排他锁,会先取得该表的IX锁
意向锁的作用是 在有行锁操作时,给该表加上一个标记,表示该表中已有锁,不需要进行全表扫描

LOCK tables xxx ,加锁,UNLOCK tables 解锁(先 commit 再解锁) ,仅当auto_commit=0才能加

juejin.cn/post/685512…

四.主从同步

image.png

五.调优

youzhixueyuan.com/MySQL-slow-…