mysql数据库笔记

文章目录


mysql

锁力度

  • 全局锁

    • 对整个数据库实例加锁,mysql中的力度最大的锁,用于数据备份。Flush tables with read lock
  • 表级锁

    • 表锁主要是一些非事务性存储引擎用(MyISAM)

      • 表锁的语法是 lock tables ... read/write

      • 表结构变更,加索引字段列

      • 安全的给小表加锁:kill掉长事物,暂停ddl或者在 alter table 语句里面设定等待时间,超时放弃再重试

  • 页级锁

    • 对数据页加锁,力度在表级锁和行级锁之间
  • 行级锁

    • mysql的行锁是存储引擎自己实现的

    • 锁对象颗粒度小,每次获取和释放锁消耗大,且容易死锁

      • 两阶段锁协议:锁在需要时获取,事务结束时释放,不是在不需要锁的时候释放。rc隔离级别是执行完释放-

      • 启示:事物需要锁住多个行时,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放

      • 死锁:并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源

        • 1.配置超时时间,但是时间不能太短,防止误判正常锁等待

        • 2.发起死锁检测,发现死锁回滚其中某一事物,但是面对1000并发更新同一行数据场景检测性能消耗很大

          • 通过将一行改成逻辑上的多行来减少锁冲突,类似于LongAdder的分段锁思想,降低并发度
    • 行级锁不是指锁一行,记录锁,间隙锁,临间锁

兼容性

  • 共享锁

    • 读读不阻塞
  • 排他锁

    • 读写阻塞,写写阻塞
  • 注意:锁是在需要时获得上锁,事务结束时才释放锁,不是不需要了就释放锁

加锁机制

  • 乐观锁

    • CAS实现,version字段解决ABA问题
  • 悲观锁

    • 假定发生冲突,屏蔽一切违反数据完整性的操作

加锁模式

  • 记录锁

    • 对比表中单行记录加锁,行锁
  • 间隙锁

  • 临检锁

    • 某条记录以及这条记录前面间隙加锁,特殊的间隙锁
  • 意向锁(表锁)InnoDB自动加的,为了让行锁和表锁更高效并存,意向锁主要与表级的排他/共享锁互斥

    • 意向共享锁

    • 意向排他锁

在mysql中锁是针对于索引的,如果覆盖索引,锁会加到辅助索引上而不是主键索引

  • 辅助索引只是真实数据备份,查询直接返回即可,涉及更新操作仍会根据辅助索引查询的主键去锁主键索引

问题集

sql查询一行长时间不返回

  • 1.等待MDL锁:一个线程正在表 t 上请求或者持有 MDL 写锁(表加字段)

  • 等待Flush,所查询的数据所在数据也正在flush,但一般flush很快,可能是flush被其他操作堵塞了,flush堵塞了查询sql

  • 所查询数据所在行正在写入,等待写锁(行锁),读写冲突

  • 查询慢,undolog版本链太长了,视图是在开始事务生成的,期间数据更新了n次,查询的数据需要很多次版本链计算

对于vachar(10)查询条件超过10长度mysql会截取,然后索引查询

  • 随后再server层做等值判断

提问题

  • mysql为什么要使用varchar数据类型,直接使用char不香吗?

    • 不香,varchar的字符存储量更大,且vachar通过额外的1-2个字符存储字符长度,动态分配空间。而char的定长是多少就占多少空间

      • 连续存储:由于长度固定,每条记录的每个 CHAR 字段在表中都占用相同的空间,数据存储在物理磁盘上也是连续的。
        直接访问:因为每条记录的每个字段长度固定,数据库引擎可以通过简单的计算偏移量快速定位和读取数据。这种直接访问的方式可以减少计算时间和复杂度,从而提高检索速度。
    • 为什么要有char:由于是定长,CHAR 类型在存储和检索时性能更高,尤其是对于固定长度的字符串。因为 MySQL 可以直接通过偏移量访问特定位置的数据。

  • 为什么只有主键索引是聚簇索引?辅助索引不能创建聚簇索引?

    • 辅助索引也创建为聚簇索引,叶节点存储数据量过大,且辅助索引选择的字段也要限制非空唯一,不方便
  • 为什么说InnoDB索引的性能更好?MyISAM也是B+树存储结构

    • MyISAM存储结构叶子节点存储的是数据的磁盘文件地址,虽然节约了空间,但它不是聚簇索引,还要进行额外的磁盘IO,效率低
  • 为什么要先记录redolog之后再异步写入数据,直接写入数据不香吗?

    • redo log的写入是磁盘顺序写,效率很高,直接写表要在不同表的不同位置去写,可能还涉及索引更改,异步去写等着吧,攒的一些一块写,或者并发低了再写,减少IO

Server层

mysql设计思想:内存足够,多利用内存,减少磁盘访问

Pool 设计

  • 牺牲部分内存资源,将高频访问数据长期驻留内存,以空间换取更快的响应速度。

  • Buffer Pool 将频繁访问的数据页和索引页缓存在内存中,避免每次查询都从磁盘读取。

    • LRU 优化:InnoDB 使用改进的 LRU 算法,将 Buffer Pool 分为"新生代"和"老生代",避免全表扫描污染缓存。

    • 这个LRU算法是改进了的,把链表分为young和old,新加载的内存页直接进入old区,被访问间隔小于1s就不删除,存在时长超过1s移到头部

  • Change Buffer:缓存非唯一索引的变更操作,减少磁盘随机写

  • Adaptive Hash Index:自动为热点索引创建哈希索引,加速等值查询

  • mysql内存&磁盘临时表

    • 临时表一般用于处理比较复杂的计算逻辑(分库分表系统的跨库查询,join场景)。由于临时表是每个线程自己可见的

    • 内存临时表建立时若内存空间不足则会转变为磁盘临时表

      • 根据sort_buffer_size参数配置确认

      • join_buffer_size

    • 何时使用

      • union并集查询

      • groupby没有索引

      • order

连接器

  • 建立维护管理连接,验证权限。客户端8h没动静连接断开。数据库连接池就是用池化技术去管理数据库连接这个资源。建立连接很复杂,推荐使用长连接减少建立连接操作

    • 长连接:连接成功后一直使用同一个连接处理客户端请求

    • 长连接内存暴涨:mysql执行时是用的内存管理在连接对象里的,长连接积累会导致内存占用过大

      • 定期断开长连接,或者初始化长连接

查询缓存

  • 拿到一个查询请求后,会先到查询缓存,命中直接返回结果。一般不开启,mysql8.0删除了

  • 失效频繁,表存在更改查询缓存就失效了,中率会非常低

分析器

  • 进行mysql的词法分析,语法分析,判断sql是否正确及其种类

优化器

  • 生成执行计划,选择索引,有join时,确定join连接顺序,and场景顺序,sql先后顺序优化等

    • 选错索引问题:索引上不同值的个数称为基数(区分度),优化器通过采样统计计算基数(不准确),而走索引未覆盖还需要回表查询,所以特殊情况下优化器会全表扫描而不是走索引

      • 解决方案1:force index强制索引

      • 方案2:修改sql条件引导走索引:比如建立ab联合索引,就用order by a,b

执行器

  • 表权限操作判断,操作存储引擎,返回结果

存储引擎

InnoDB特性

  • 插入缓冲

    • 先写redolog,插入操作涉及索引维护等,将插入操作缓存在插入缓冲区,延迟批量处理,WAL机制,减少磁盘写IO

      • 一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(粉板)里面,并更新内存,数据页不在内存中,使用ChangeBuffer记录这个操作。这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面

      • 有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。

  • 自适应哈希

    • 动态的在内存区创建哈希表以应对频繁访问查询

脏页

  • 内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为"脏页"

    • mysql抖一下(间歇性性能下降):把内存里的数据写入磁盘的过程flush,就是将脏页刷入磁盘

    • 刷脏页场景

      • 1.redo log写满了,需要将一些脏页刷入磁盘,清除部分redolog才能继续写

      • 2.内存满了,需要把内存的数据页删除,若为干净页,直接删除,脏页刷入磁盘再删除

      • 3.mysql稍微空闲,或者要关闭时刷脏页

空间分配

  • mysql表删除一条数据后,只是标记改位置数据可用,主键id400删除后,表空间没有释放,只是这个位置可以记录复用,再有id400插入后可以复用这个位置

  • 表的整个数据页数据被删除后,该块内存也可以复用到任何位置,没有id限制

  • 要释放表空间,可以用drop table

  • 因此数据删除,数据页分裂会造成空洞,可以用重建表解决

    • alter table A engine=InnoDB:Online DDL这个操作执行时原表继续提供服务,执行流程:扫描数据页-生成B+树-记录这段时间操作生成日志文件,临时表替换新表

    • 表很大查询高时影响性能,慎用

存储引擎的选择

  • 一般都用InnoDB,处理大量读操作时MYISAM可能会更高效

  • B树和B+树的区别

    • 单个B+树非叶子节点节点可以存储1170个主键和指针 ,叶子节点存储约16行数据(一行1k),叶子节点内部有单向链表连接记录,叶子节点间双向链表连接。

      • 层级计算

      • 第一层:1 个根节点。

      • 第二层:1170 个节点 → 可管理 1170 * 1170 ≈ 1,368,900 叶子页。

      • 第三层:1,368,900 * 1170 ≈ 1.6 亿 叶子页(足以覆盖 6,250,000 页)

    • 更矮(非叶子节点可存更多键,减少层级),查询都到达叶子结点,更稳定,叶子节点双向链表适合范围查询

InnoDB与MyISAM区别

  • 事务

    • MyISAM不支持事务,InnoDB支持事务
    • MyISAM

      • 仅支持表操作,读不影响,写会锁整表
    • InnoDB

      • 采用多版本并发控制MVCC实现读写分离,读写冲突少
  • 外键

    • MyISAM不支持外键约束,InnoDB支持外键
  • 索引实现

    • MyISAM

      • 使用B树索引,支持全文索引,索引文件中存储数据文件磁盘地址(指针)
    • InnoDB

      • 使用聚簇索引,主键索引和数据存储在一起,支持建立非主键索引,未覆盖索引可拿查询的主键值回表查询

索引

简介:帮mysql获取数据的排好序的数据结构,底层主要用B+树实现,B+树的一个节点就是一个数据页(16k)

索引常见模型

  • 为什么使用B+树

    • 二叉树会退化成链表,红黑树高度过高,B+树自平衡且稳定,只在叶子节点存储数据,非叶子节点作为索引,检索速度快且稳定,减少了磁盘IO且支持范围查询
  • 哈希表:适用于只有等值查询的场景,范围查询不行

  • 有序数组:等值查询和范围查询场景中的性能就都非常优秀,但是只适用于静态存储引擎,插入成本太高

  • 二叉搜索树:二叉搜索效率太低,树层级过高

优缺点

  • 优点:加快数据检索速度,在查询过程中提高系统性能

  • 缺点:创建和维护索引耗费时间,索引占用物理空间

    • 使用费有序索引作主键会导致频繁页分裂,影响性能,降低空间利用率,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

索引覆盖

  • 使用select进行查询时,如果查询的字段在多索引中都存在,则不需要获取主键通过主键索引再进行回表查询了

索引下推

  • 索引下推:减少回表查询次数,提高查询效率,判断WHERE条件部分能否用索引中的列来做检查,减少去主键索引取值对比的次数

    • Innodb中索引下推只适用于二级索引

索引类型

  • 主键索引

    • 主键索引所选字段值唯一,且不含NUll,InnoDB中主键索引就是聚簇索引,主键索引存储全部真正的数据,所以叫聚簇。无主键,则选择一列符合条件的字段作为主键,否则会用隐藏字段ROWID作为主键(2的48次方,超出从0重开始),一般表都要建主键,且主键推荐位int自增类型,方便索引维护和范围查询(叶子节点中存储相邻结点的磁盘文件地址,方便范围查询)

      • 自增主键:自增主键可以让主键索引尽量地保持递增顺序插入,避免了页分裂,因此索引更紧凑

      • InnoDB 中,自增值是被记录在内存的。MySQL 8.0 才给 InnoDB 表的自增值加上了持久化的能力,确保重启前后一个表的自增值不变

      • 业务层靠自增字段判断并不可靠

        • 自增锁:非事务性锁,老版本一条语句执行完释放新版本获取到最大id值就释放,批量插入则批量获取完释放,无需等待事务结束再释放自增锁
    • mysql支持多主键,聚簇索引只有一个,但是mysql支持联合主键索引

  • 辅助索引

    • 唯一索引

      • 唯一指的是表中的所有值都是唯一的(email,phone),允许NULL,且一张表中可以有多个唯一索引。

        • 唯一索引无法使用ChangeBuffer,因为唯一索引更新数据页需要查询是否数据冲突,必须载入内存了,所以在业务能保证数据唯一的前提下,推荐选择普通索引
    • 普通索引

      • 用于加速数据检索,值可重复,也叫辅助索引,叶子节点存储的是主键值和索引列值

        • ChangeBuffer:更新一个数据页时,若数据页不在内存中,会将这个指令写入ChangeBuffer中,等到下次需要访问该数据时,把改数据页读入内存再执行ChangeBuffer内容。

          • changeBuffer是可持久化的,为了减少磁盘随机读IO,而redo是为了减少磁盘随机写IO

          • 在一个数据页做 merge(真正进行数据更新) 之前,change buffer 记录的变更越多,收益越大

    • 前缀索引

      • 取字段的前几个字节作为索引,不比如邮箱身份证号等建立索引

        • 使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本

        • 使用前缀索引就用不上覆盖索引对查询性能的优化了

        • 倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题;

    • 全文索引

      • 对文本字段进行全文搜索的查询。它适用于查找文本中的特定单词或短语,适用于 CHAR, VARCHAR, 或 TEXT 类型的列,并且可以在多个列上创建
    • 联合索引

      • 底层还是用B+树实现的,根据字段先后优先级依次排序,查询时要用索引需要满足最左前缀原则,不只是where,order也要算入

      • 例:建立 a,b,c的联合索引后,若查询语句为select where a=,b=,c=时条件判断无论顺序,都会被优化器优化为abc的顺序,走索引,若为a=,c=则会使用部分索引(a)去过滤,c不走索引,过滤后的去走主键索引 再次查询

      • 联合索引中的后续列在前面有范围查询或模糊匹配时,通常只能用于匹配,而无法起到快速缩小结果集的作用,(匹配过程就是索引下推)

        • 多个等值查询在使用联合索引时会有更好的效果
  • 主键索引存储的是真实存储的数据,而辅助索引存储的是索引数据和主键的备份,辅助索引更新,主键索引也会更新对应的字段值

  • explain

    • all:全表扫描。效率极低

    • ref:使用非唯一索引查找

    • ref-of-null:使用索引查找行,并且可能返回 NULL 值

    • range:用索引来查找一个范围内的值

    • index:使用索引中的所有列来查找行,覆盖索引了

索引结构

  • B+树索引

    • 值存储在叶子节点中,其余节点存储多个索引列信息和子节点指针,主键索引是聚簇索引,叶子节点存储数据,辅助索引叶子节点只存储主键和索引列数据,叶子节点形成一个有序的双向链表,方便范围查询和顺序扫描。它通过页分裂和页合并来实现树的平衡
  • Hash索引

    • 和数据结构中的Hash表实现基本一致,使用Hash表时,主要用Hash算法将数据库字段转换成定长Hash值,与这条数据的行指针一起存入Hash表中,哈希冲突拉链法解决

    • InnoDB会监控对表中各索引的查询,如何可以带来速度提升则会自动建立哈希索引,且自动删除。Memory引擎中有显式Hash索引可建立,范围查询难

索引设计原则

  • 在where语句中常出现的列适合建索引,频繁查询的字段要建索引

  • 基数小的表适合建索引

  • 一般一个数据库建立2-3个联合索引就合适了,单个索引建立使用的比较少(一个(a,b)索引相当于有单个a索引了)不要过度索引

  • 建立的联合索引要满足需求的最左前缀原则

  • 索引优化

    • 在建立联合索引的时候,如何安排索引内的字段顺序

      • 通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的

      • 考虑的原则就是空间,a,b字段都需要建立但索引,a占空间大,用ab联合索引再单独建一个b索引

  • 对于那些查询中很少涉及的列,重复值比较多(基值低)的列不要建立索引。

索引失效

  • 违背最左前缀原则,查询语句where,order字段必须和索引从左到右一致,否则第一个不一致的字段及以后字段无法使用索引

    • 只要有部分满足最左前缀,就可以利用索引来加速检索,减少回表查询的次数

    • like搜索破坏最左前缀原则

      • 如果把 % 放在了前面,最左的 n 个字母便是模糊不定的,无法根据索引的有序性准确的定位到某一个索引,只能进行全表扫描,找出符合条件的数据。
    • 只有当最左边的列被用作等值条件或者也参与了范围查询时,右侧的列才能有效地用于进一步的过滤,可以进行范围或模糊匹配

  • 索引列上运算,索引失效

    • 对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能

      • 优化器偷懒:对于不改变有序性的函数也不走索引
    • 比如mysql> select count(*) from tradelog where month(t_modified)=7;用了month函数

  • 隐式类型转换索引失效

    • 比如select * from tradelog where tradeid=110717;

      • vachar(32)转int,比较规则改变了,mysql优化器不走索引
  • 隐式字符编码转换

    • 两个表的字符编码不同,查询同一个表的数据插入另一个表,同一字段字符编码不同,索引失效
  • mysql评估索引慢,则不会用

    • 场景:未覆盖索引,查询的数据量大,走索引还要回表查询,小表扫描场景也是

    • 近似全表查询的场景

  • in走索引,not in索引会失效

    • 案例:select * from t where city in ("杭州"," 苏州 ") order by name limit 100; 这个 SQL 语句是否需要排序

      • 要用到 (city,name) 联合索引的特性

      • sql拆解select * from t where city="苏州" order by name limit 100再用归并排序的思想

  • 存在or时,or条件设计的每个列都要单独有索引索引才会生效或者(一个联合索引能覆盖也生效)

场景题

mysql分布式主键

  • 在分布式情况下不推荐使用自增主键

    • 数据划分范围存储,a存满b存储,并没有分担a库压力

    • 共用一套id,需要主键同步,效率低下

    • 分用id,跨库查询出现问题

  • 使用uuid

    • 用自增主键设计利于b+树构造和维护,但是uuid会导致树的页分裂,结构变化,效果差

    • mysql8.0推出BIN_TO_UUID将指定二进制uuid转为uuid,利于B+树构造

  • 雪花算法

    • 全局唯一,趋势递增,根据时间戳生成的

    • 41位时间戳+10位机器码+12位序列号

      • 机器码:不同机器,服务号

      • 序列号:取号

    • 时钟回拨问题

      • 服务器与真实时间不一致

      • 雪花算法生成重复id

      • 比对,抛异常,补偿机制

思考:在数据库查询中处理排序、分组和筛选(SQL层处理)与将原始数据拉到Java业务层处理,对比

  • 查询筛选数据,减少数据传输量

  • 分组排序等,复杂条件筛选,利用数据库的索引和算法,效率更高

  • 对于更为复杂的业务逻辑处理,无法用sql表达,在业务层处理

性能优化

sql&索引优化

  • 添加中间表

    • 对于需要联合查询的数据,添加中间表转为对中间表的联查

      • 用户具有多个身份,单个身份被多个用户持有,使用中间表可以简化复杂的查询,减少重复计算
  • 排序性能优化

    • 如果需要对结果内容排序,对排序字段建立适当的索引,提高效率
  • 大批量数据更新:分批更新

  • orderBy

    • 一般需要排序

      • 全字段排序

        • 对数据只读取一边,基于内存排序,内存不够借助磁盘辅助

        • 不用在排序后从磁盘查询其他字段了

      • rowid 排序

        • 当要查询的字段过多时,内存不够用,就只取要排序字段和主键排序,再进行一次主键查询

        • 是否借助磁盘排序取决于排序所需的内存和参数 sort_buffer_size。

          • 外部排序一般使用归并排序算法
      • 排序过程要生成临时表,排序是因为其原来的数据都是无序的,若数据有序可以走索引(部分覆盖/全覆盖)则无需排序或无需全部排序

    • 对于临时表是内存表,回表过程只是简单地根据数据行的位置,直接访问内存得到数据,根本不会导致多访问磁盘,所以用rowId法排序

    • 随机化排序

      • 1.order by rand()显示随机消息,代价大

      • 2.生成主键随机数查询(数据空洞问题)

      • 3.MySQL 处理 limit Y,1 的做法就是按顺序一个一个地读出来,丢掉前 Y 个,然后把下一个记录作为返回结果,因此这一步需要扫描 Y+1 行

  • 优化子查询

    • 子查询尽量用多表联查替代

      • 为什么:子查询会为每一行执行一次内部查询。这可能会导致大量重复的计算,尤其是在处理大数据集时,性能会显著下降。
  • limit优化

    • 数据量大时,比如要查询100000条数据的最后10条数据,偏移量较大时使用索引,或者可以子查询查出关联id减少数据扫描量
  • insert优化

    • 使用批量插入,统一提交

      • IO
  • count优化

    • InnoDB没有像MYISAM一样缓存数据条总数,因为事务MVCC,直接 count(*) 会遍历全表,虽然结果准确

    • 数据量过大时可以缓存总行数,定期更新。如果对准确性要求不高,可以定期更新缓存,redis实现,redis宕机再查一下放上去,不精确。(这两个不同的存储构成的系统,不支持分布式事务,无法拿到精确一致的视图)

      • 计数直接放到数据库里单独的一张计数表 C 中
    • 不要count字段/主键,使用count(1)(取出每一行不取值,填入1)或count(),count()mysql做了优化,按行累加

  • join优化

    • 一般用小表作为驱动表(扫描驱动表入join buffer),前提是被驱动表的索引可以使用,使用join的效率比查单表高

      • 在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是"小表",应该作为驱动表。
    • 尽可能让被驱动表有索引可用,否则被驱动表全表扫找对应join条件字段很慢(优化器会去做)

      • 应用层拆分成多个语句然后再拼接查询结果更方便
    • 业务层哈希索引优化:select * from t1;取得表 t1 的全部 1000 行数据,在业务端存入一个 hash 结构,select * from t2 where b>=1 and b<=2000; 获取表 t2 中满足条件的 2000 行数据。

      把这 2000 行数据,一行一行地取到业务端,到 hash 结构的数据表中寻找匹配的数据。

      • 大表join影响bufferPool的内存,降低内存命中率
  • groupby优化

    • 1.新建索引列及其索引,比如groupby a%10就新建z列,列中元素为a%10 Using index,表示这个语句使用了覆盖索引。

    • 数据量大且不适合建立索引的使用SQL_BIG_ RESULT建立磁盘临时表进行groupby操作

架构优化

  • 主备复制

    • 主备库切换

      • 可靠性策略

        • 主库改为只读,备库的seconds_behind_master参数变为0后(主备无延迟),备库改为可读写。存在不可读写期间,业务阻塞
      • 可用性策略

        • 直接切换备库可读写,这是主备可能不一致有binlog需要处理,可靠性差
      • 一般来说可靠性大于可用性

    • 复制策略:多线程并行复制,颗粒度:按行分发,按表分发,按库分发

    • 过期读

      • 主库更新完,从库查询到过期的没来得及更新的数据

      • 处理策略

        • 强制走主库,把查询请求分类,需要准确读实时的走主库读

        • 读取从库之前从库先sleep一下

  • mysql分库分表&读写分离

    • 其实这块我实操的比较少,这块我们做后端实操的不多

    • 用的最多的就是,我们做项目可能有一个当前表和一个历史表,会定期进行一个历史数据的迁移这样。这就是一个时间分割

    • 读写分离

      • 为了解决mysql的QPS过大的问题提出的方案,部署mysql主从集群,主库负责处理写请求,从库同步更新,处理读请求,分担主库压力
    • 分库分表

      • 为了解决数据量过大IOPS过高,查询频率过高时,查询量会很大

      • 分库

        • 随着服务的拆分去分数据库,一般用的比较少

        • 或者水平分库,数据量过大,建立平行库

      • 分表

        • 水平分表

          • 数据格式和列是一样的

          • 就是把一个表的数据分别存到多个表同格式的中

        • 垂直分表

          • 把前几个字段做成一个表,后几个字段做成一个表
      • 分片策略

        • 哈希切片

          • 对数据库的某个字段进行来求哈希,再除以分片总数后取模,取模后相同的数据为一个分片,这样将数据分成多个分片的方法叫做哈希分片
            我们大多数在数据没有时效性的情况下使用哈希分片,就是数据不管是什么时候产生的,系统都需要处理或者查询;
        • 时间切片

          • 按照时间的范围将数据分布到不同的分片上,比如我们可以将交易数据按照与进行切片,或者按照季度进行切片,由交易数据的多少来决定按照什么样的时间周期来进行切片
            这种切片方式适合明显时间特点的数据,常见的就是订单历史查询
        • 分布式事务

          • 依靠数据库本身的事务机制不能满足需要,这时就需要用到分布式事务来解决了

          • 两阶段提交,阻塞式

            • 两阶段提交协议中的两阶段是:准备阶段和提交阶段,两个阶段都是由事务管理器(协调者)发起,事务管理器能最大限度的保证跨数据库操作的事务的原子性。
          • 补偿重试

硬件优化

  • 最有效但成本最大的优化方式,费钱

事务

acid

  • 原子性

    • 事务是最小的执行单位,同时成功失败,主要通过undolog实现,命令后出了问题要回滚,回滚依靠undo log+roll-pointer,每一次数据变更就有一条undo log生成

    • undolog内部就有回滚指针,回滚指针形成链表

  • 一致性

    • 使用事务要达成的最终目的
  • 隔离性

    • 事务并发,内部不互相干扰

    • mysql提供了四种隔离级别来保证隔离性,用MVCC和锁机制来实现

  • 持久性

    • 事务提交,结果会永久保存,不会丢失

    • mysql通过将redo log和数据文件写入磁盘来保证持久性

    • WAL机制:数据提交时先写redo log,redo log和 binlog 都是顺序写,磁盘的顺序写比随机写速度要快,降低磁盘的 IOPS 。 redo log日志写成功数据就算更改成功了,对数据文件的修改是异步进行的

      • redolog写入:redo生成后是在内存redolog Buffer中的,接着到在文件系统的 page cache,然后调用 fsync 持久化到磁盘。具体事务提交后redolog是否写入磁盘是由配置策略决定的

        • PageCache 就是操作系统在内存中给磁盘上的文件建立的缓存,和业务中的redis类似
      • InnoDB 有一个后台线程,每 1 秒把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。

      • redo是循环写的,比如只有4G的大小,写满或mysql空闲时要刷磁盘更新数据,删除redo.WAL写后再读数据,内存有数据可以直接返回,不一定读磁盘

    • binlog

      • redo log:记录的是物理层面的数据页更改,是存储引擎特有的(内存一份,磁盘一份)

        binlog:server层的,记录sql的原始逻辑,追加写的,不会删除覆盖。

      • 两阶段提交

        • 进行数据恢复时binlog记录近期逻辑修改,定期做整库备份,基于某个备份执行binlog恢复

          • 只要 redo log 和 binlog 保证持久化到磁盘,就能确保 MySQL 异常重启后,数据可以恢复
        • 为了让两份日志之间的逻辑一致,redo成功bin失败,数据恢复存在问题,redo失效,事物回滚。

        • 把redo分为prepare和commit阶段,两个阶段直接想写binlog,准备-提交-同时回滚或提交(分布式事务一致性也用两阶段提交 )

          • 数据库崩溃时若binlog未写完 则会回滚,提交操作进行时崩溃,则检查binlog是否完整,完整则提交(binlog特殊的数据格式判断 是否完整)
      • binlog写入: 先将日志写入binlog cache线程自己维护的,binlog写入是不可打断的,事务提交后再将cache数据写入binlog日志文件中

        • 每个线程有自己 binlog cache,但是共用同一份 binlog 文件。一般我们推荐双1配置
      • 日志存储方式

        • STATEMENT

          • 记录的是实际执行的SQL语句,缺点是如果在主从复制环境中,从库可能因为执行同样的SQL在不同数据集上得到不同的结果而出现不一致(例如使用了-now()这样的函数
        • ROW

          • ROW格式的binlog记录的是数据更改的每一行的实际变化情况,而不是SQL语句。在数据量大或频繁更新的情况下,ROW格式可能会导致较大的日志量和更高的磁盘I/O需求
        • MIXED

          • MIXED格式是一种折衷方案,它根据SQL语句的特性自动选择STATEMENT或ROW格式来记录。

隔离级别

  • 隔离级别越高,并发效率越低

  • 读未提交

    • read uncommit,会读到其他事务还未提交的数据,在进行读操作时不加锁,一般不用

      • 脏读,不可重复读,幻读
  • 读已提交

    • 每次读会读取最新提交的版本,在同一事务中两次读同一数据会有不同结果RC特征

      • 避免读写未提交数据

      • 不可重复读,幻读

      • 读提交下操作数据的锁范围更小,没有间隙锁

  • 可重复读

    • 在一次事务中,对某个数据的读取结果一直是第一次读到的结果,但会读到旧数据(默认)快照读

      • 幻读
    • 多条查询语句在可重复的的隔离级别下必须使用事务,保证读取的数据都是同一版本的

      • 可重复读解决的是事物的一致性问题,保证前后不同时间点读取的数据在业务上是完整的
    • 可重复读升级串行化

      • 数据刚执行插入,并没有数据,无法读取之前版本的快照,必须串行化,插入结束后再读数据,否则无法读取
  • 串行化

    • 事务在没有commit时阻塞,不可读,并发效率过低,一般不用

脏写,幻读,不可重复读

  • 脏读

    • 读取到未提交的数据,或前一个事务回滚了,读到了前一个事务未提交的数据

    • 出现在读未提交隔离级别中,采用更高的隔离级别,读已提交(readView只能读到已提交数据)等解决

  • 不可重复读

    • RC隔离级别下,同一个session中的同一sql两次查询结果不一样
  • 幻读

    • 一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。

    • 在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在"当前读"下才会出现(select for updete)。

    • 间隙锁,一个事务在执行范围查询或等值查询更新时等,mysql会在这个范围内的间隙加上锁,阻止事务向这个范围内插入新数据,避免幻读

      • 执行写入时原来的判断依据发生了变化

      • 间隙锁是在可重复读隔离级别下才会生效的

      • 间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的

    • 解决幻读,是为了保证查询结果不变,操作的依据不变以应对并发问题。实质解决办法加锁使得查询数据的依据不变

MVCC

mysql隐藏字段

  • trx_id

    • 每个事务开始时都会获取mysql中的maxTransactionId再+1作为事务id,事务更新数据时会把这个id赋值给trx_id
  • row_id

    • 行号,可作隐藏主键

      • 隐藏字段ROWID作为主键(2的48次方,超出从0重开始)覆盖,不推荐
  • roll_ptr

    • 回滚指针

ReadView

  • 事务开始或第一个事务sql执行瞬间创建的一个视图,记录这时的所有活跃事务id

快照ReadView在MVCC中的作用

  • 1.事务启动时会生成readView(当前活跃事务ID)活跃指启动了未提交,其中id最小值为低水位,最大值+1为高水位。

  • 2.根据上述低高水位将trx_id分类,低水位之下为已提交事务,高水位之上为未开始事务,中水位区域在ReadView事务中的为未提交事务,不在则是已提交事务,可读到

    • 版本未提交,不可见;

      版本已提交,但是是在视图创建后提交的,不可见;

      版本已提交,而且是在视图创建前提交的,可见。

    • 更新数据都是先读后写的,而这个读,只能读当前的值,称为"当前读"

  • 事务中如果有写操作,更新数据都是先读后写,这个读是当前读,读的是最新的数据

  • 可重复读场景下一个事务只创建一个视图,而读以提交场景下每执行一个语句就计算出一个新的视图

  • mysql同一条数据的多个版本不是物理意义上的,存储的是最新版本的数据,需要旧版本数据时通过回滚指针+undolog日志去计算获取

好处

  • 并发写数据时,读操作不会阻塞,写操作也不会阻塞读操作,提高了数据库的并发读写性能

  • 有效解决了脏读幻读不可重复读等问题

解决问题

  • 脏读

    • 读取时在readView中不会读取未提交数据,只能读取上一个版本数据
  • 不可重复读

    • 读取时,每次都复用同一个readView来保证每次读取的数据一致

    • 同一个事务中,对同一行数据的两次读取结果不一致

  • 幻读

    • 一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行(新行,修改不算)

    • 在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在"当前读"下才会出现。当前读:select for update,update。

      • MVCC同一个视图也可以用来解决幻读,对于for update用间隙锁解决
    • 引入间隙锁解决,间隙锁之间不冲突,间隙锁和插入冲突(间隙锁只在RR隔离级别下有)

      • 间隙锁和行锁合称 next-key lock,每个 next-key lock 是前开后闭区间,id=5后一条id=10,锁(5,10]
    • 锁的范围是针对于索引的,如果覆盖索引,主键索引上就不会加锁。

    • 原则 1:加锁的基本单位是 next-key lock。next-key lock 是前开后闭区间。

      原则 2:查找过程中访问到的对象才会加锁。

      优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。

      优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。

      一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

    • RR隔离级别下:事物开启,select update select会导致第二个select不一致

数据类型

整数类型

  • tinyInt

    • 8位二进制整数
  • smallInt

    • 16位二进制整数
  • mediumInt

    • 24位二进制整数
  • int

    • 32位二进制整数

小数类型

  • float

    • 单精度浮点数
  • double

    • 双精度浮点数
  • decimal

    • 严格压缩的定点数

日期类型

  • year

    • YYYY
  • time

    • HH:MM:SS
  • date

    • YYYY-MM-DD
  • datetime

    • YYYY-MM-DD HH:MM:SS
  • timestamp

    • YYYY-MM-DD HH:MM:SS

文本,二进制类型

  • CHAR(M)

    • CHAR(0-255),存储短字符串,内容过长会截断

    • char:由于是定长,CHAR 类型在存储和检索时性能更高,尤其是对于固定长度的字符串。因为 MySQL 可以直接通过偏移量访问特定位置的数据。

  • VACHAR(M)

    • 存储可变长字符串,通过用额外的1-2个字节存储字符串长度实现高效存储

数据库设计三范式

第一范式,确保每一列保持原子性。

第二范式,确保表中的每列都和主键相关。

第三范式,确保每列都和主键列直接相关,而不是间接相关。

实际数据库设计中,出于性能或特定需求的考虑,可能会适当进行反范式化

相关推荐
Full Stack Developme21 分钟前
CRM相关库表设计
数据库
步行cgn24 分钟前
Spring 注入 Map 集合详解
数据库·python·spring
今儿敲了吗27 分钟前
03停用词过滤
笔记·python
风跟我说过她1 小时前
SQL 一键转经典 Chen 风格 ER 图:开源 CLI + 在线工具 + Agent Skill
数据库·python·sql·开源·开源软件
这个DBA有点耶1 小时前
MySQL大表DDL锁表锁到崩溃?Online DDL的3个关键参数和实战避坑
数据库·mysql·架构
秦哈哈2 小时前
【Hello Agents】学习笔记(二)
笔记·学习·microsoft
2302_1112 小时前
java依赖小结
java·运维·数据库
听到微笑3 小时前
Elasticsearch 如何存储与检索海量向量
数据库·elasticsearch
风早爽太3 小时前
Flutter 开发笔记:share_plus 分享插件
笔记·flutter
海浪仙人掌3 小时前
速动比率怎么分析?速动比率分析有哪些注意事项?
大数据·数据库·人工智能