数据库

mysql 相关

2021-06-17  本文已影响0人  wxxhfg

索引

底层结构

hash表索引缺点:

  1. 利用hash存储需要将所有的文件添加到内存,比较耗费内存空间

  2. 如果所有的查询都是等值查询,那么hash确实很快,但是在实际场景中查找的数据更多,而且不都是等值查询,因此hash表就不太合适

  3. 不支持范围快速查找,范围查找时还是只能通过扫描全表方式。

二叉树索引格式缺点:

  1. 不论是二叉树还是红黑树,都会因为树的深度而造成io次数变多,影响数据读取的效率。

  2. 平衡二叉树不支持范围查询快速查找,范围查询时需要从根节点多次遍历,查询效率不高

B树索引格式:

  1. 若非叶子结点中的数据过大,比如每次1.9k,磁盘大小为4k。此时会退化成二叉树,会出现树的深度加深而造成io次数变多,影响读取效率。

  2. B树不支持范围查询的快速查找,你想想这么一个情况如果我们想要查找10和35之间的数据,查找到15之后,需要回到根节点重新遍历查找,需要从根节点进行多次遍历,查询效率有待提高。

  3. 如果data存储的是行记录,行的大小随着列数的增多,所占空间会变大。这时,一个页中可存储的数据量就会变少,树相应就会变高,磁盘IO次数就会变大。

  4. 联合索引一定满足最左原则,若跳过最左的第一个索引,直接从第二个索引开始搜索的话,第二个索引不一定是排好序的,会需要进行全栈搜索。

InnoDB和MyIsam

mysql一页 有16kb大

MyIsam

1. MyISAM的数据文件和索引文件是分开存储的。MyISAM使用B+树构建索引树时,叶子节点中存储的键值为索引列的值,数据为索引所在行的磁盘地址。

InnoDB中Data存储的为行数据,而MyIsam中存储的是磁盘地址。

2. 在 MyISAM 中,辅助索引和主键索引的结构是一样的,没有任何区别,叶子节点的数据存储的都是行记录的磁盘地址。只是主键索引的键值是唯一的,而辅助索引的键值可以重复。

3. 查询数据时,由于辅助索引的键值不唯一,可能存在多个拥有相同的记录,所以即使是等值查询,也需要按照范围查询的方式在辅助索引树中检索数据。

InnoDB

innoDB将所有的信息一起存放,包括数据和索引

主键索引(聚簇索引)

每个InnoDB表都有一个聚簇索引 ,聚簇索引使用B+树构建,叶子节点存储的数据是整行记录。一般情况下,聚簇索引等同于主键索引,当一个表没有创建主键索引时,InnoDB会自动创建一个ROWID字段来构建聚簇索引。InnoDB创建索引的具体规则如下:

  1. 在表上定义主键PRIMARY KEY,InnoDB将主键索引用作聚簇索引。
  1. 如果表没有定义主键,InnoDB会选择第一个不为NULL的唯一索引列用作聚簇索引。
  1. 如果以上两个都没有,InnoDB 会使用一个6 字节长整型的隐式字段 ROWID字段构建聚簇索引。该ROWID字段会在插入新行时自动递增。

在中InnoDB,辅助索引中的叶子节点存储的数据是该行的主键值,在检索时,InnoDB使用此主键值在聚簇索引中搜索行记录。

根据加锁的范围,MySQL里面的锁大致可以分成全局锁、表级锁和行锁三类

全局锁

全局锁就是对整个数据库实例加锁。

在此状态下,其他线程进行下列操作会被阻塞:

  1. 数据的更新

  2. 表的定义(表的建立,和结构的修改)

  3. 更新类事务的提交

使用场景:

做全库逻辑备份,即将整库每个select出来成文本。

缺点:

表锁

MySQL里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)

表锁需要显示的加上,针对于访问线程。如果在某个线程A中执行lock tables t1 read,t2 wirte;这个语句,则其他线程写t1、读写t2的语句都会被阻塞。同时,线程A在执行unlock tables之前,也只能执行读t1、读写t2的操作。连写t1都不允许

MDL不需要显式使用,在访问一个表的时候会被自动加上。MDL的作用是,保证读写的正确性。在对表进行增删改查时,对表进行mdl读锁;对表进行结构变更,对表加上mdl写锁。

行锁

MySQL的行锁是在引擎层由各个引擎自己实现的。但不是所有的引擎都支持行锁,比如MyISAM引擎就不支持行锁

两段锁协议

将事务分成两个部分,加锁和解锁。

死锁和死锁检测

解决死锁的两种策略:

如果所有事务都要更新同一行的场景,每个新来的被堵住的线程都要判断会不会由于自己的加入导致死锁,这是一个时间复杂度是O(n)的操作

怎么解决由这种热点行更新导致的性能问题?

  1. 如果确保这个业务一定不会出现死锁,可以临时把死锁检测关掉

  2. 控制并发度

  3. 将一行改成逻辑上的多行来减少锁冲突。以影院账户为例,可以考虑放在多条记录上,比如10个记录,影院的账户总额等于这10个记录的值的总和。这样每次要给影院账户加金额的时候,随机选其中一条记录来加。这样每次冲突概率变成员原来的1/10,可以减少锁等待个数,也就减少了死锁检测的CPU消耗

mysql 内存和日志

内存模型

mysql内存模型

Buffer Pool模型

Buffer Pool模型

buffer pool里面有三个链表:

free链表 :其作用是管理空闲页,实际上存储的是一个个小控制块 ,控制块中存放的是对应结点的地址指针

flush链表:其作用是管理脏页,结构和free链表相同 ,但表达意义不同

lru链表:其作用是进行淘汰页、将最近最久未使用的页替换出来。通常将lru链表分成热数据部分和冷数据部分,比例默认为5 : 3

日志

日志分布图

redo log(物理日志)

  1. 若脏页没有来得及刷新到磁盘中,mysql挂掉了。可使用redo log进行恢复 -------> 读取磁盘中原来的数据 + redolog 中的操作 == 在原数据上将以前的操作再做一遍,以达到恢复的效果。

  2. 重做时 ,只需要顺序的读取redolog ,是顺序io,速度快。

redolog更新

     磁盘中的redolog file默认是2个 , 当两个都写满了的时候,会触发检查点,将其中某个logfile文件中的操作结合buffer pool中的脏页刷新到磁盘中。 例如: 0号文件满了 , 将继续写入当1号文件。若1号文件也满了,后续动作是触发checkpoint , 将0号文件中的对应的脏页 刷新到磁盘中,后将redo log记录覆盖到0号文件中。

redolog持久化

即: 将log Buffer中的缓存写入到磁盘中(redo log)。

更新时机(innoDB_flush_log_at_trx_commit参数控制)

配置值         描述
0 表示提交事务时,不立刻对redo log进行持久化,这个任务交给后台线程去做(相当于定时刷新)
1 表示事务提交时,立刻把redo log进行持久化 (默认使用
2 表示事务提交时,立即将redo log写入操作系统缓冲区,并不会直接将redo log进行持久化,这种情况下,如果数据库挂了,但是操作系统没挂,那么事务的持久性还是可以保证的。

bin log(逻辑日志)

bin log 和redo log区别

bin log中记录的是一条一条的sql语句,redolog 记录的是某页上的某个位置。当恢复时,binlog需要重新进行sql的写入更改(需要查询等) , redolog可以直接进行修改。

undo log

相当于与原来的sql 进行相反的操作

数据更新写入流程图

数据更新写入流程图

二阶段提交

mysql一页有16kb , 而操作系统一页为4kb , 故将数据库页数据写到磁盘中需要4次。若写的某一次中系统掉电了,则会使得数据不一致。

使用双写缓冲区保证一致性

1、写入双写缓冲区,写入成功,则进行后续;没写成功,也没有影响,数据库仍然处在一致性状态

2、写入表空间,写入完成则进行数据持久化完成。若还未写入完成时,掉电,也没有影响,double write buffer 的数据时持久化好的,可以直接使用。

update操作

  1. 更新buffer pool里面的页里面的数据

  2. 生成一个redo log

  3. commit,持久化 这个redo log对象

操作流程:

  1. server服务层的执行器,调用存储引擎层的API接口,去查询数据。

  2. innodb存储引擎层查询buffer pool缓存中的数据。

  3. 如果查询缓存中包含待查询的数据,则直接返回给server服务层的执行器。

  4. 如果缓存中没有结果则从磁盘中去读取数据,读取数据后,再返回给server服务层,同时把查询到的数据更新到buffer pool中的数据内容。

  5. server层的执行器收到查询后的数据后,执行更新操作。

  6. server层调用innodb存储引擎层的API接口更新数据。

  7. innodb 存储引擎层更新数据到change buffer缓存池中。

  8. innddb存储引擎层记录redolog,并把其状态设置为prepare状态。

  9. innodb存储引擎层通知server的执行器,change buffer已经更新,redolog已经进入prepare待提交的就绪状态,可以记录binlog日志的。

  10. server层的执行器记录binlog到binlog的缓冲池中。(这里缓冲池中的日志何时刷盘就是通过参数sync_binlog来控制的)

  11. server层的执行器在记录完binlog之后,通知innodb存储引擎层,binlog已经记录完成。

  12. innodb存储引擎层收到server记录完binlog的通知后,更新redolog buffer中的redolog为commit状态。(此时redolog buffer中的日志何时刷盘就是通过参数innodb_flush_log_at_trx_commit来控制的。)

事务和隔离级别

ACID特性实现原理

隔离级别

  1. 读未提交 (read-uncommitted) 可能出现脏读 会话a会获得其他会话的未提交到数据库中的数据

  2. 读已提交 (read - committed) 可能出现不可重复读

    会话b开启事务、修改数据、但未提交,此时b可以看到当前的数据 会话a看到的还是修改前的数据。即,只 有一个事务的提交完成后,另外一个会话才能看到修改后的值

    问题:不可重复读,在同一个事务中查询两次表,出现的结果不一样

    • 1. 会话a开启事务,会话b也开启事务。两个事务同时进行查询,出现结果一致,假设结果为22.
    • 2. 会话b,对值进行修改为99,提交事务。此时会话b再次读取值为修改后的值99.
    • 3. 会话a再次读取值,值也为99. 此时会话a的事务还未结束,本应该出现的值为修改前的22,此时值为99.出现了 不可重复读现象,即读取一张表的同一数据,结果不一致。
  3. 可重复读 (repeatable - read) 可能出现幻影读

    自己的事务没有提交或者回滚,是不能看到事务的操作的。

    问题:

    幻影读:第二次查询比第一次查询多了记录。

    1. 会话a和会话b都开启事务。
    1. 会话b对表新增一条数据,提交。此时会话a对表进行了第一次查询,因为还未提交或者回滚事务,会话b的事务影响不到会话a的事务,即:会话看到的还是以前的数据。
    1. 会话a此时提交或者回滚事务。进行第二次查询,此时查询数据多出来一条数据。出现幻影读
  4. 串行化 (serializable)

    进行查询或者修改的时候会对表进行加锁操作,保证是串行进行

    脏读、不可重复读、幻读

MVCC 解决脏读 和 不可重复读

幻影读解决

使用间隙锁解决幻影读

innoDB的锁机制原理

InnoDB行锁:给索引上的索引项加锁

  1. 共享锁

  2. 排他锁

  3. 意向共享锁

  4. 意向排他锁

  5. 自增锁

  6. 临键锁

> < between 
    锁定区间左开右闭
  1. 间隙锁
临键锁没有匹配到记录,就会退化成间隙锁
  1. 记录锁
顾名思义,记录锁就是为某行记录加锁,它封锁该行的索引记录.该索引必须是主键锁或者唯一索引列。

脏页刷新

部分刷新 fuzzy checkpoint

  1. 无论如何,定时刷新

    当LRU中列表中空闲页不足时,强制LRU删除一些末尾的页,如果存在脏页,那么需要checkpoint刷新

  2. master thread中的定时刷新机制

    1)InndoDB1.0.x版本之前的master thread。

    每秒,会进行一次 dirty too much checkpoint

    每10秒

    判断过去10秒的IO操作是否小于200次,如果是,刷100个脏页;

    判断系统当前脏页比例,如果超过70%,刷新100个;如果小于70%,刷新脏页的10%

    2)InndoDB1.2.x版本之前的master thread。

    在1.0.x存在硬编码,每秒最多只会刷新100个脏页到磁盘中,这种规定其实限制了性能更高的SSD磁盘。

    在1.0.x版本,可以使用innodb_io_capacity来表示磁盘io的吞吐量。刷新脏页的数量由innodb_io_capacity来控制,默认是200。

    总结

全部刷新

sharp checkpoint:在数据库关闭时,刷新所有的脏页到磁盘,这里有参数控制,默认是开启的

上一篇 下一篇

猜你喜欢

热点阅读