文章目录
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:由于是定长,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个字节存储字符串长度实现高效存储
数据库设计三范式
第一范式,确保每一列保持原子性。
第二范式,确保表中的每列都和主键相关。
第三范式,确保每列都和主键列直接相关,而不是间接相关。
实际数据库设计中,出于性能或特定需求的考虑,可能会适当进行反范式化