1、三大范式
1、第一范式
原子性:保证每列不可再分(属性不可分)
2、第二范式
在满足第一范式前体下,每张表仅描述一件事情(每个非主属性完全函数依赖于键码)
3、第三范式
在满足第一范式和第二范式前体下,表中数据均与主键直接相关,不能间接相关(非主属性不传递函数依赖于键码)
2、事务
ACID
数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对同一个数据的读取结果都是相同的。
3、并发一致性问题(4)
4、封锁
粒度,类型,三级协议,隔离级别
MySQL默认支持的隔离级别是 REPEATABLE-READ
。为什么呢?
并发一致性问题 | 解决原因 | 封锁协议 | 隔离级别 |
---|---|---|---|
丢失修改 | 排他写锁 | 一级封锁协议 | RU(READ UNCOMMITTED) |
读脏数据 | 排他读锁(读完数据马上释放锁) | 二级封锁协议 | RC(READ COMMITTED) |
不可重复读 | 排他读锁,引入间隙锁(事务结束才释放锁) | 三级封锁协议 | RR(REPEATABLE READ) |
幻影读 | 加入邻健锁(Next-key Locks) | 两段锁协议 | 可串行化(SERIALIZABLE) |
5、MVCC
Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。MVCC 不能解决幻影读问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。
-
Record lock:单个行记录上的锁
-
Gap lock:间隙锁,锁定一个范围,不包括记录本身
-
Next-key lock:record+gap 锁定一个范围,包含记录本身
版本号
-
系统版本号 SYS_ID:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。
-
事务版本号 TRX_ID :事务开始时的系统版本号。
Undo 日志
MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 ROLL_PTR 把一个数据行的所有快照连接起来。
ReadView
MVCC 维护了一个 ReadView 结构,主要包含了当前系统未提交的事务列表 TRX_IDs {TRX_ID_1, TRX_ID_2, ...},还有该列表的最小值 TRX_ID_MIN 和 TRX_ID_MAX。
快照读与当前读
6、ER图,项目如何设计的
商户服务:每个表都有一个主键id。商户表,应用表,员工表等
交易服务:每个表都有一个主键id。订单表,支付渠道表(微信,支付宝),支付渠道参数表,平台支付渠道(c2b,b2c等)
7、B+Tree
红黑树
红黑树是满足如下条件的二叉搜索树:
-
每个结点要么是红色,要么是黑色
-
根节点是黑色
-
每个叶结点(NIL结点,空结点)是黑色的
-
不能有相邻的两个红色结点
-
从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点
对于数据在内存中的情况(如上述的TreeMap和HashMap),红黑树的表现是非常优异的。但是对于数据在磁盘等辅助存储设备中的情况(如MySQL等数据库),红黑树并不擅长,因为红黑树长得还是太高了。当数据在磁盘中时,磁盘IO会成为最大的性能瓶颈,设计的目标应该是尽量减少IO次数;而树的高度越高,增删改查所需要的IO次数也越多,会严重影响性能。
B+ Tree 是基于 B Tree 和叶子节点顺序访问指针 进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。
B+ 树的结构
优点:
① B+ 树非叶子节点上是不存储数据的,仅存储键值,而 B 树节点中不仅存储键值,也会存储数据。
在数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的 IO 次数又会再次减少,数据查询的效率也会更快。
② B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。
B+ 树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而 B 树因为数据分散在各个节点,要实现这一点是很不容易的。
B+ 树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。
B+树与B树的不同
-
B+树非叶子节点不存在数据只存索引,B树非叶子节点存储数据
-
B+树查询效率更高。B+树使用双向链表串连所有叶子节点,区间查询效率更高(因为所有数据都在B+树的叶子节点,扫描数据库 只需扫一遍叶子结点就行了),但是B树则需要通过中序遍历才能完成查询范围的查找。
-
B+树查询效率更稳定。B+树每次都必须查询到叶子节点才能找到数据,而B树查询的数据可能不在叶子节点,也可能在,这样就会造成查询的效率的不稳定
-
B+树的磁盘读写代价更小。B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,通常B+树矮更胖,高度小查询产生的I/O更少。
InnoDB 的 B+Tree 索引分为主索引和辅助索引。主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。
8、最左前缀匹配原则
最左前缀匹配原则指的是,在使用联合索引时,MySQL 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成,或者在执行过程中遇到范围查询(如 >、<)才会停止匹配。对于 >=、<=、BETWEEN、like 前缀匹配的范围查询,并不会停止匹配。所以,我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。
由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDER BY子句也遵循此规则。
9、索引
索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。
事务是逻辑上的一组操作,要么都执行,要么都不执行。
1.索引优化(5)
独立的列
在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。
例如下面的查询不能使用 actor_id 列的索引:
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
应该养成简化 WHERE条件的习惯, 始终将索引列单独放在比较符号的一侧
多列索引
在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。
SELECT film_id, actor_ id FROM sakila.film_actor WHERE actor_id = 1 AND film_id = 1;
索引列的顺序
让选择性最强的索引列放在前面。
索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1(例如主键),此时每个记录都有唯一的索引与其对应。选择性越高,每个记录的区分度越高,查询效率也越高。
前缀索引
对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。
前缀长度的选取需要根据索引选择性来确定。
覆盖索引
索引包含所有需要查询的字段的值,被查询的列,数据能从索引中取得,不需要再从主索引中进行回表,而不用通过行定位符row-locator再到row上获取,即“被查询列要被所建的索引覆盖”,这能够加速查询速度。
覆盖索引(covering index ,或称为索引覆盖)即从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,显著提升性能。
2.优点,缺点,使用条件。什么时候使用?
索引的优点
-
大大减少了服务器需要扫描的数据行数。
-
帮助服务器避免进行排序和分组,以及避免创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,不需要排序和分组,也就不需要创建临时表)。
-
将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起)
索引的缺点
-
创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
-
索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
-
当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
索引的使用条件
-
对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效;
-
对于中到大型的表,索引就非常有效;
-
但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术
1.有or必全有索引;
2.复合索引未用左列字段;
3.like以%开头;
4.需要类型转换;
5.where中索引列有运算;
6.where中索引列使用了函数;
7.如果mysql觉得全表扫描更快时(数据少);
1.唯一性差;
比如性别,只有两种可能数据。意味着索引的二叉树级别少,多是平级。这样的二叉树查找无异于全表扫描。
2.频繁更新的字段不用(更新索引消耗);
比如logincount登录次数,频繁变化导致索引也频繁变化,增大数据库工作量,降低效率。
3.where中不用的字段;
只有在where语句出现,mysql才会去使用索引
4.索引使用<>时,效果一般;
5.索引过多或过大:如果数据库中存在大量的索引,或者某个索引非常大,可能会导致索引失效或降低性能。过多的索引会增加数据库的存储空间和维护开销,而过大的索引可能会导致内存不足或I/O开销增加。
10、优化查询性能
优化数据访问(4);重构查询方式(2)
11、存储引擎
1.MyISAM,InnoDB
-
事务:InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句。
-
并发:MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
-
外键:InnoDB 支持外键。
-
备份:InnoDB 支持在线热备份。
-
崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
-
其它特性:MyISAM 支持压缩表和空间数据索引
-
是否支持 MVCC
2.MySQL 中的 InnoDB 引擎如何实现事务的 ACID 特性
当向数据库写入数据时,会先写入到Buffer Pool中,Buffer Pool中更新的数据会定期刷新到磁盘中(此过程称为刷脏)。
执行一个数据操作语言DML(Data Manipulation Language)语句时,会执行如上图的操作
-
将修改后的数据存入缓冲池(Buffer Pool)中,等待刷脏。
-
在Log Buffer中写入重做日志(Redo Log),并将日志状态设置为Prepare。
-
返回MySQL服务层,记录BinLog日志。
-
将Log Buffer中的日志文件状态设为Commit,并等待日志文件存入盘中。
以上的等待刷脏、等待事务日志文件刷盘操作不是串行,而是交由后台进程并发处理
以上的事务提交过程称为XA的两阶段提交
回滚机制
在Innodb引擎中事务的回滚机制是依托回滚日志(Undo Log)进行回滚数据的保证的.
在事务异常中断,或者主动(Rollback)回滚的过程中,Innodb基于 Undo Log进行数据撤销回滚,保证数据回归至事务开始状态.
Doublewrite Buffer是开在共享(系统)表空间的物理文件的 buffer,其大小是2MB.
刷脏操作开始之时,先进行脏页备份操作.将脏页数据写入 Doublewrite Buffer.
将Doublewrite Buffer(顺序IO)写入磁盘文件中(共享表空间) 进行刷脏操作.(绝大多数是随机IO)
Double Write机制其核心思想是: 在刷脏之前,建立脏页数据的副本.系统意外宕机造成页断裂的情况可通过脏页数据副本 (DoubleWrite Buffer)进行恢复.
隔离性的实现主要依赖两大技术
-
LBCC(Lock Based Concurrency Control)
事务开始操作数据前,对其加锁,阻止其他事务对数据进行修改。
针对SQL的操作,使用当前读解决并发读写的隔离性问题。(当前读:读取到的数据是线程独占的、最新的数据)
-
MVCC(Multi Version Concurrency Control)
事务开始操作数据前,将数据在当下时间点进行一份数据快照(Snapshot)的备份,并用这个快照来提供给其他事务进行一致性读取 。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 READ-COMMITTED(读取提交内容) ,但是你要知道的是 InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读) 并不会有任何性能损失。InnoDB 存储引擎在 分布式事务 的情况下一般会用到 SERIALIZABLE(可串行化) 隔离级别。
🌈 以下内容摘自《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章:
12、四个日志
bin log、redo log、undo log和慢查询日志
undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。需要注意的是,undo页面的修改,同样需要记录redo日志。
在InnoDB存储引擎中,大部分情况下 Redo 是物理日志,记录的是数据页的物理变化。而逻辑Redo日志,不是记录页面的实际修改,而是记录修改页面的一类操作,比如新建数据页时,需要记录逻辑日志。关于逻辑Redo日志涉及更加底层的内容,这里我们只需要记住绝大数情况下,Redo是物理日志即可,DML对页的修改操作,均需要记录Redo.
Undo log 不是 Redo log 的逆过程.
Bin log
作用:用于复制,在主从复制中,从库利用主库上的 binlog 进行重播,实现主从同步。 用于数据库的基于时间点的还原
内容:逻辑格式的日志,可以简单认为就是执行过的事务中的 sql 语句。但又不完全是 sql 语句这么简单,而是包括了执行的 sql 语句(增删改)反向的信息,也就意味着 delete 对应着 delete 本身和其反向的 insert;update 对应着 update 执行前后的版本的信息;insert 对应着 delete 和 insert 本身的信息。
binlog 有三种模式:Statement(基于 SQL 语句的复制)、Row(基于行的复制) 以及 Mixed(混合模式)
bin log 和 redo log 的区别:
-
redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用
-
redo log 是物理日志,记录的是 “在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如 “给 ID=2 这一行的 c 字段加 1 ”
-
redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写” 是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志
不使用两阶段提交可能会造成数据不一致的问题。
-
先写 redo log,后写 bin log。在写入 redo log 后,未写入 binlog,此时若数据库发生异常,当再次启动数据库后,由于 redo log 正常,数据库内容没有问题,若想使用 bin log 进行备份,则会出现数据不一致的问题。
-
先写 bin log,后写 redo log。写入 bin log 而未写入 redo log 时,若数据库发生异常,当再次启动数据库后,由于 redo log未写入该数据,认为该数据无效,但是在 bin log 中多了一条数据,造成数据不一致的问题。
-
在写 redo log prepare 阶段崩溃,时刻 A 的位置。重启后,发现 redo log 没写入,回滚此次事务。
-
如果在写 binlog 时奔溃,重启后,发现 binlog 未被写入,回滚操作。
-
binlog 写完,但在提交 redo log 的 commit 状态时发生崩溃.
-
如果 redo log 中事务完整,有了 commit 标识,直接提交。
-
如果 redo log 中只有完整的 prepare, 判断对应 binlog 是否完整。
-
完整,提交事务
-
不完整,回滚事务。
-
-
这样就解决了数据一致性的问题。
需要注意的是,undo页面的修改,同样需要记录redo日志。
13、分库分表,分页
1、垂直切分
垂直切分常见有垂直分库和垂直分表两种。
垂直分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。
垂直分表是基于数据库中的"列"进行,某个表字段较多,可以新建一张扩展表,将不经常用或字段长度较大的字段拆分出去到扩展表中。在字段很多的情况下(例如一个大表有100多个字段),通过"大表拆小表",更便于开发与维护,也能避免跨页问题,MySQL 底层是通过数据页存储的,一条记录占用空间过大会导致跨页,造成额外的性能开销。另外数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘IO,从而提升了数据库性能。
优点:
-
解决业务系统层面的耦合,业务清晰
-
与微服务的治理类似,也能对不同业务的数据进行分级管理、维护、监控、扩展等
-
高并发场景下,垂直切分一定程度的提升IO、数据库连接数、单机硬件资源的瓶颈
缺点:
-
部分表无法join,只能通过接口聚合方式解决,提升了开发的复杂度
-
分布式事务处理复杂
-
依然存在单表数据量过大的问题(需要水平切分)
2、水平切分
水平切分分为库内分表和分库分表,
库内分表只解决了单一表数据量过大的问题,但没有将表分布到不同机器的库上,因此对于减轻MySQL数据库的压力来说,帮助不是很大,大家还是竞争同一个物理机的CPU、内存、网络IO,最好通过分库分表来解决。
水平切分的优点:
-
不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力
-
应用端改造较小,不需要拆分业务模块
缺点:
-
跨分片的事务一致性难以保证
-
跨库的join关联查询性能较差
-
数据多次扩展难度和维护量极大
3.分库分表带来的问题和解决方法(5)
1、事务一致性问题
解决方法:分布式事务和最终一致性
分布式事务:当更新内容同时分布在不同库中,不可避免会带来跨库事务问题。一般可使用"XA协议"和"两阶段提交"处理。
最终一致性:对于那些性能要求很高,但对一致性要求不高的系统,往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。与事务在执行中发生错误后立即回滚的方式不同,事务补偿是一种事后检查补救的措施,一些常见的实现方法有:对数据进行对账检查,基于日志进行对比,定期同标准数据来源进行同步等等。事务补偿还要结合业务系统来考虑。
2、跨节点关联查询 join 问题
切分之后,数据可能分布在不同的节点上,此时join带来的问题就比较麻烦了,考虑到性能,尽量避免使用join查询。
解决方法:全局表、字段冗余、数据组装、ER 分片
3、跨节点分页、排序、函数问题
跨节点多库进行查询时,会出现limit分页、order by排序等问题。分页需要按照指定字段进行排序,当排序字段就是分片字段时,通过分片规则就比较容易定位到指定的分片;当排序字段非分片字段时,就变得比较复杂了。需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户。
4、全局主键避重问题
解决方法:UUID,结合数据库维护主键ID表,分布式雪花算法
5、数据迁移、扩容问题
当业务高速发展,面临性能和存储的瓶颈时,才会考虑分片设计,此时就不可避免的需要考虑历史数据迁移的问题。一般做法是先读出历史数据,然后按指定的分片规则再将数据写入到各个分片节点中。此外还需要根据当前的数据量和QPS,以及业务发展的速度,进行容量规划,推算出大概需要多少分片(一般建议单个分片上的单表数据量不超过1000W)
4.什么时候切分(5)
14、总体架构
注意查询缓存!
分成了server层和引擎层。
15、MySQL 百万级别数据查询优化
1.以下行为将会导致全表扫描,应当避免:
-
where 中使用 !=或<、>
-
where 中使用 null,is null可以使用索引,is not null无法使用索引
-
where 中使用 or,OR 前后只要存在非索引的列,都会导致索引失效
-
where 中使用前置 %
-
where 中使用 in 或 not in
-
where 中使用函数操作或字段表达式
-
如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引
2.类型的使用
-
尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会 逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
-
尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
3.应该避免的使用
-
避免频繁创建和删除临时表,以减少系统表资源的消耗。
-
临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
-
如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除
-
-
尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
-
尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
-
尽量避免大事务操作,提高系统并发能力。
4.查询慢的原因
优化原因:
-
没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷)
-
没有创建计算列导致查询不优化。
-
查询出的数据量过大(可以采用多次查询,其他的方法降低数据量)
-
返回了不必要的行和列
-
查询语句不好,没有优化
系统原因:
-
I/O吞吐量小,形成了瓶颈效应。
-
内存不足
-
网络速度慢
其他:
-
锁或者死锁(这也是查询慢最常见的问题,是程序设计的缺陷)
-
sp_lock,sp_who,活动的用户查看,原因是读写竞争资源。通过执行sp_lock存储过程,可以获取当前会话或进程所持有的锁以及其他会话或进程所持有的锁的相关信息
16、使用Explain
一、定位慢SQL
-
1.首先确认是否开启了慢查询
2.设置慢查询的时间限制
3.查询慢查询日志可定位具体的慢sql
4.相关sql查询
5.用Explain分析具体的sql语句