数据库基础知识
范式
- 第一范式: 属于第一范式关系的所有属性都不可再分,即数据项不可分。
- 第二范式: 要求实体的属性完全依赖于主关键字。(其余的字段和主键关系很强)
- 要求一个数据库表中不包含已在其它表中包含的非主关键字信息,即数据不能存在传递关系,即每个属性都跟主键有直接关系而不是间接关系。(订单中存放产品名称) ,要进行关联
反范式
- 为了性能和读取效率而适当的违反对数据库设计范式的要求
- 为了查询的性能(索引命中会多),允许存在部分(少量)冗余数据
- 对比:
- 反范式的实现
- 缓存(缓存本身就是冗余)与数据汇总
- 冗余经常要使用的别的表的字段,不用关联
- 冗余方便统计的字段
- 计算器
- 因为更新是只能更新一条数据,影响并发,可以利用槽的概念增加更新的数据条数,提高并发
- 因为更新是只能更新一条数据,影响并发,可以利用槽的概念增加更新的数据条数,提高并发
- 缓存(缓存本身就是冗余)与数据汇总
优化
- 字段类型
- 整数类型
- tinyint (8位)、smallint (16位)、mediumint (24位)、int (32位)、 bigint(64位) , 1、2、3、4、8个字节
- integer 和Int 是一样的, int(UNSIGNED)无符号会存更多的正数
- int(1) 和int(11) 存储的大小是一样的,只是为了显示字符的个数
- 实数类型
- float(4个字节)、double(8个字节)、decimal(65个数字)
- DECIMAL类型用于存储精确的小数,本质上MySQL是以字符串形式存放的。所以CPU不支持对DECIMAL的直接计算,只是在MySQL中自身实现了DECIMAL的高精度计算
- 精度不敏感和需要快速运算的时候,选择FLOAT和 DOUBLE。
- 在数据量比较大的而且要求精度时,可以考虑使用BIGINT代替DECIMAL,将需要存储的货币单位根据小数的位数乘以相应的倍数即可
- 字符串类型
- var(定长)、varchar(不定长)、blob(二进制)、text(字符)、enum、set
- char (4)定长更省空间。没有记录的长度 (定长的CHAR类型不容易产生碎片)
- vachar (255)不定长:1-2额外字节记录字符串的长度。
- char和varchar选择
- 字符串列的最大长度比平均长度大很多,变化很大用varchar
- 定长的字符用char
- BLOB和TEXT都是为存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储。对索引不友好,需要单独进行索引优化, 每个值在行内需要1~4个字节存储一个指针,然后在外部存储区域存储实际的值
- 整数类型
CREATE TABLE
enum_test(e ENUM(' fish', 'apple', 'dog') NOT NULL);
6. 枚举中存储的整数不是字符串(类似于java的枚举),可以节约空间,类型选择enum,在值中定义具体的枚举内容,mysql在查询中进行显示的优化
- 日期和时间类型 (mysql的最小时间粒度为秒)
- datetime 、timestamp (跟时区有关系)
- datetime 存储日期范围:1001年~9999年
- timestamp 存储日期范围:1970年~2038年,并且跟时区有关系。
- 如果需要存储比秒更小粒度的日期和时间值, 可以使用BIGINT类型存储微秒级别的时间截
- 字段类型优化
- 更小的通常更好
- 是有一个类型既可以用字符串也可以使用整型,优先选择整型.因为字符串牵涉到了字符集及校对规则等。
- 避免为null
- 通常情况下最好指定列为NOT NULL,除非真的需要存储NULL值(对系统的提升很小,但尽量)
- 因为可为NULL的列使得索引、索引统计和值比较都更复杂。可为NULL的列会使用更多的存储空间,在MySQL里也需要特殊处理。当可为NULL的列被索引时,每个索引记录需要一个额外的字节
- 如果计划在列上建索引,就应该尽量避免设计成可为NULL的列
- 更小的通常更好
- 字段命名规范
- 不使用复数名词,因为数据库中的数据原本就是多条
- 禁用保留字,如desc、range、match之类
- 索引命名, 主键索引名为pk_字段名;唯一索引名为uk_字段名;普通索引名则为idx_字段名
索引
- 索引
- InnoDB引擎常见的索引: B+树索引 全文索引 哈希索引
- 哈希索引
- 哈希索引只能等值查询(查询很快),不能实现范围查询,也不能排序
- 使用组合索引的时候不能只通过一个字段查询,hash冲突
- B+树索引(利用二分查找)
- B+树是多叉树
- 二分查找和顺序查找
- 顺序查找平均查找次数为(1+2+3+4+5+6+7+8+9+10)/10=5.5次。而二分查找法为(4+3+2+4+3+1+4+3+2+3)/10=2.9次。在最坏的情况下,顺序查找的次数为10,而二分查找的次数为4
- 树: 二叉树 二叉查找树 平衡二叉树(AVL树)
- B+树 (多叉平衡查找树)
- B树
- B+树
- B*树非叶子节点也用指针相连(oracle实现)
- https://www.cs.usfca.edu/~galles/visualization/Algorithms.html 演示地址
- 标准的B+数叶子节点单链表。MySQL实现升级(双链表<—>)
- 右边的比左边的大,高度差不超过1
- 所有的元素都在叶子结点,右边大于左边
- 叶子结点按照链表相连
- 非叶子节点只保存索引和下一个页的指针信息
- 类似于跳表
数据库索引
B+树索引
- 聚集索引/聚簇索引
- 通过过聚集索引能获取完整的整行数据。另一个优点是:对于主键的排序查找和范围查找速度非常快。
- 没有唯一性索引,MySQL也会创建一个隐含列RowID来做主键
- 辅助索引/二级索引
- 一个索引就会有一个B+树
- 叶子节点并不包含行记录的全部数据,内容是相应行数据的聚集索引键
- 查询具体的行内容需要回表
- 回表的记录越多,使用二级索引的性能就越低,回表的次数过多,会进行全表扫描(查询优化器)
- 联合索引/复合索引
- 联合索引叶子节点的内容(上边是note的索引内容):
- 最左优先排 : 先按note列进行排序创建索引,note相同的内容的字段b的内容再进行排序
- 叶子节点包含多个数据和主键
- 覆盖索引
- 辅助索引中的列就可以将数据全部查询到,不需要进行回表
- B+树的查找次数,取决于B+树的高度, B+树的高度一般为3、4层,故需要3、4次的IO查询
哈希索引
- Hash索引知乎已查询一次
- InnoDB存储引擎内部自己去监控索引表,如果监控到某个索引经常用,那么就认为是热数据,然后内部自己创建一个hash索引,称之为自适应哈希索引( Adaptive Hash Index,AHI)
- 自适应哈希索引仅是数据库自身创建并使用的,我们并不能对其进行干预
索引的作用
- 一个select查询语句在执行过程中一般最多能使用一个二级索引,即使在where条件中用了多个二级索引,如果使用多个二级索引会导致回表的次数过多,导致查询性能降低
- 索引的列尽量小
- 创建索引应该选择选择性/离散性高的列, (离散性)不重复的索引值(也称为基数,cardinality)和数据表的记录总数(N)的比值
前缀索引
- 针对blob、text、很长的varchar字段,mysql不支持索引他们的全部长度,需建立前缀索引。
- 语法:Alter table tableName add key/index (column(X)), X 表示第几个前缀
- 前缀索引是一种能使索引更小、更快的有效办法,但另一方面也有其缺点MySQL无法使用前缀索引做ORDER BY和GROUP BY,也无法使用前缀索引做覆盖扫描。
后缀索引
- MySQL原生并不支持反向索引,但是可以把字符串反转后存储,并基于此建立前缀索引。可以通过触发器或者应用程序自行处理来维护索引
索引的建立
- 只为用于搜索、排序或分组的列创建索引
- 只为出现在WHERE 子句中的列、连接子句中的连接列创建索, 又或者为出现在ORDER BY或GROUP BY子句中的列创建索引
三星索引
- 索引将相关的记录放到一起(缩短查询的叶子节点的范围)则获得一星 (比重27%)查询的条件是索引的左字段可以实现
- 如果索引中的数据顺序和查找中的排列顺序一致则获得二星(查询出来的顺序和索引的顺序一致)(排序星) (比重23%) order_by的字段是索引的左字段可以实现
- 如果索引中的列包含了查询中需要的全部列则获得三星(覆盖索引,不需要回表) (比重50%)
- 优先不需要回表,顺序和索引一致 和 缩短查询的叶子节点范围 看情况选择
Mysql调优
调优类型
- 架构调优: 数据合适不合适 ,es ,mq ,redis ; 是否需要读写分离, 数据的安全
- mysql调优: 表设计,sql语句优化,索引使用
- 硬件和os系统调优: SSD硬盘,磁盘整列(RAID)
查询优化
慢查询原因
- 核心原因: 查询的数据太多了
- 优化数据的访问: 是否访问了不需要的数据(先返回全部结果集再进行计算) 例如 limit 10000:20
- 总是查询出所有的列(需要进行回表操作,会产生io操作,会降低查询性能), 数据做缓存可以查询所有的列
- 重复查询相同的数据 (可以用缓存)
慢查询标准
- 响应时间: 响应时间是两个部分之和:服务时间和排队时间
- 扫描的行数和访问类型(在EXPLAIN语句中的type列反应了访问类型)
解决方案
- 使用覆盖索引
- 优化数据库的表结构
- 重写复杂的sql
慢查询日志
- 慢查询是指mysql记录所有执行超过long_query_time参数设定的时间阈值的SQL语句的日志
- show VARIABLES like ‘slow_query_log’; set GLOBAL slow_query_log=1; 开启慢查询
- show VARIABLES like ‘%long_query_time%’; set global long_query_time=0; 设置慢查询的时间分界
- show VARIABLES like ‘%slow_query_log_file%’; 查看慢查询存放日志的位置
- show VARIABLES like ‘%log_queries_not_using_indexes%’; 没有使用索引的数据是否存放到慢sql中
- 慢查询工具: mysqldumpslow命令,对慢sql日志进行分析过滤
执行计划
- union和union all
- UNION 会合并两个或多个 SELECT 语句的结果集,但会去除重复的行 (会用到临时表)
- UNION ALL 会合并两个或多个 SELECT 语句的结果集,包括重复的行
- id 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id (in查询mysql优化器有时会将sql优化为连接查询) id为null说明使用了临时表
- select_type
- SIMPLE:简单的select查询,不使用 union及子查询
- PRIMARY:最外层的 select查询
- UNION: UNION 中的第二个或随后的 select查询,不依赖于外部查询的结果集
- UNION RESULT: UNION结果集
- SUBQUERY:子查询中的第一个select查询,不依赖于外部查询的结果集
- DEPENDENT UNION (dependent union): UNION 中的第二个或随后的 select 查询,依赖于外部查询的结果 子查询中的union
- DEPENDENT SUBQUERY:子查询中的第一个 select查询,依赖于外部查询的结果集
- DERIVED:用于from 子句里有子查询的情况。MySQL会递归执行这些子查询,把结果放在临时表里。
- type
- 执行计划的一条记录就代表着MySQL对某个表的执行查询时的访问方法/访问类型,其中的type 列就明了这个访问方法/访问类型是个什么东西,是较为重要的一个指标,结果值从最好到最坏依次是:
- system(查询系统表) > const(主键或者普通索引查询,只匹配到一条数据) > eq_ref(连接查询,被驱动表通过主键等值(主键)匹配) > ref(普通索引) > fulltext(全文索引)> ref_or_null(普通索引查询,普通索引中可以为null,查询条件中有为null) > index_merge (索引合并,不是用到两个索引) > unique_subquery(子查询和外部进行了关联) > index_subquery > range(进行了索引的范围查询) > index (使用了覆盖索引,但需要扫描全部的记录)> ALL(全表扫描)
- 出现比较多的是system>const>eq_ref>ref>range>index>ALL
- 一般来说,得保证查询至少达到range级别,最好能达到ref
- possible_keys与key
- possible_keys列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些,key列表示实际用到的索引有哪些,如果为NULL,则没有使用索引
- key_length
- key_len列表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度,可以分析联合索引用了哪几个
- ref
- 字段后匹配的内容 比如 where order_no = ‘a’, ‘a’ 表示匹配的就是const(常量)
- 这个也可能是in subQuery rang
- rows : 扫描的行数
- filtered: 使用了索引匹配到数据后,根据普通where条件匹配到的百分比,普通条件过滤后的百分比
- Extra: 扩展信息,对sql的概括
- Using where: 表示在检索行之后,MySQL 服务器将过滤它们,即它将使用 WHERE 子句来排除不满足条件的行。
- Using temporary: 表示 MySQL 需要使用临时表来存储结果集,这通常发生在 GROUP BY 或 DISTINCT 操作中,如果结果集太大而无法放入内存,则可能会使用磁盘上的临时表。
- Using filesort: 表示 MySQL 需要对结果进行额外的排序操作,而不是通过索引顺序来检索行。这通常是因为 ORDER BY 或 GROUP BY 子句中的列不是按索引顺序排列的。
索引使用
查询优化器
- 一条sql在Mysql中的执行流程
- 优化: 优化SQL语句,例如重写查询,决定表的读取顺序,以及选择需要的索引等
索引使用
- 不在索引上进行任何操作,否则会使索引失效 (表达式或者函数) ,可以将函数或者表达式优化
- 尽量全值匹配, 搜索的条件和联合索引的条件全部匹配
- 最佳左前缀匹配, 搜索条件中必须出现左边的列才可以使用到这个B+树索引
- 范围条件放最后(放到右边),联合索引如果左边的字段使用了范围,后边的索引将不会生效
- 覆盖索引尽可能使用,尽量不要使用*,让覆盖索引生效
- 不等于要慎用, mysql在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
- Null/Not有影响 (设计表时列尽可能的不要声明为null)
- is not null容易导致索引失效,is null则会区分被检索的列是否为null,如果是null则会走ref(普通索引)类型的索引访问,如果不为null,也是全表扫描。可以把not null转为in查询
- 字符类型要加引号,如果字符串不加引号会使索引失效,mysql会进行类型转换
- like条件, 百分号不要放最前边
- 使用覆盖索引,可以解决这个问题
- 一个 insert_time ,order_status, expire_time 的联合索引
- explain SELECT order_status,expire_time FROM order_exp_cut WHERE insert_time like ‘%18:35:09’;
- 使用or关键字
- 如果or的字段是同一个字段,索引会生效
- 如果or的字段一个有索引,一个没有索引,会使索引失效, 可以把or转换为union all,至少可以让一个索引生效,使用覆盖索引也可以解决这个问题
- 如果or的字段两个都有索引,两个索引都会生效使用
- 使用索引列来做排序和分组
- 用来排序的列尽可能在一个联合索引里边,如果排序的两个字段都有各自的索引,联合排序会使索引失效
- 尽可能按照主键的顺序插入数据
- 插入一个比较小的数据,会使数据的叶变化大,B+树的转化会变大
- 优化Count查询
- COUNT()是一个特殊的函数,有两种非常不同的作用:它可以统计某个列值的数量,也可以统计行数
- count统计一个具体的字段时,只统计这个字段不为null的行
- 优化limit分页查询
- 在偏移量非常大的时候,会影响性能
- 优化方案加where条件判断ID where id>xxx,然后limit 0 到 xxx
- 关于Null的特别说明
- 有的认为NULL值代表一个未确定的值,MySQL认为任何和NULL值做比较的表达式的值都为NULL,包括selectnull=null和select null!=null;
- 有的任务NULL表示一个值,count(某个字段),会将NULL值排除掉
事务和隔离级别
概念
- 事务特性: 原子性(要不全部成功,要不全部失败) 一致性(总体转账前后整体的金额不变) 隔离性(事务之间不能互相影响) 持久性(做的修改要永久的保存在数据库中)
- 事务并发引起的问题
- 脏读: 当一个事务读取到了另外一个事务修改但未提交的数据,被称为脏读。
- 不可重复读: 在一个事务中执行查询内容,查询到的结果不一致
- 幻读: 在一个事务中执行查询条数,查询到的结果不一致
- 事务隔离级别
- 未提交读, 可能发生脏读、不可重复读和幻读问题
- 已提交读, 可能发生不可重复读和幻读问题,但是不可以发生脏读问题
- 可重复读, 可能发生幻读问题,但是不可以发生脏读和不可重复读的问题。(mysql的可重复读解决了幻读的问题,但没完全解决)
- 可串行化, 各种问题都不可以发生
- 设置隔离级别
- SHOW VARIABLES LIKE ‘transaction_isolation’;
- SELECT @@transaction_isolation;
- SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
- 事务执行
- 开始: begin START TRANSACTION
- 回滚: rollback ; 提交: commit
- 保存点
- 设置保存点: SAVEPOINT 保存点名称;
- 回滚到保存点: ROLLBACK TO SAVEPOINT 保存点名称;
- 隐式提交
- DDL语句(定义或修改数据库对象的数据定义)
- 在一个会话里,一个事务还没提交或者回滚时就又使用START TRANSACTION或者BEGIN语句开启了另一个事务时,会隐式的提交上一个事务
- …
- MVCC
- trx_id 事务ID undo日志,每一条操作会记录一条undo日志
- undo日志,编号从0开始
- 版本链: trx_id roll_poniter 回滚指针
-
- 读已提交每次生成的readView都不一样,第二次的80已经提交了,readView的m_ids中没有80了,所以看到了80的数据
- 可重复读,因为80虽然提交了,但还在m_ids中,所以还是看不到
- ReadView的概念
- m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
- min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
- max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4
- creator_trx_id:表示生成该ReadView的事务的事务id。
- 读已提交: 隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。
- 可重复读: 只会在第一次执行查询语句时生成一个ReadView
- 当执行查询sql时会生成一致性视图read-view
- 组成内容: min_trx_id [m_ids] max_trx_id creator_trx_id
- 版本链比对规则:(一个事务在读取数据时,会生成一个ReadView视图,然后将版本链中的一条条数据依次根据readView视图判断是否可见)
- 如果被访问数据的事务ID(trx_id)等于creator_trx_id,说明当前事务正在访问自己修改过的数据,因此该版本的数据对当前事务可见。
- 如果被访问数据的事务ID小于min_trx_id,说明生成该版本数据的事务在当前事务开始之前已经提交,因此该版本的数据对当前事务可见。
- 如果被访问数据的事务ID大于或等于max_trx_id,说明生成该版本数据的事务在当前事务开始之后才启动,因此该版本的数据对当前事务不可见。
- 如果被访问数据的事务ID在min_trx_id和max_trx_id之间,则需要进一步判断该事务ID是否在活跃事务ID列表(m_ids)中:
- 如果在m_ids列表中,说明生成该版本数据的事务在当前事务开始时仍然活跃(即未提交),因此该版本的数据对当前事务不可见。(解决了脏读的问题)
- 如果不在m_ids列表中,说明生成该版本数据的事务在当前事务开始之前已经提交,因此该版本的数据对当前事务可见。(A,B,C三个事务,查询的时候A,B没有提交,C提交了,保证能够查询到C的数据,因为C的数据小于最大的事务ID,也不在m_ids链表中)
- 如果根据以上规则判断当前版本的数据不可见,系统会通过数据行的回滚指针(DB_ROLL_PTR)查找Undo Log链中的旧版本数据,并重复上述匹配逻辑,直到找到对当前事务可见的数据版本或确定该记录对当前事务完全不可见。
- 幻读问题: 事务A中: 查询一个数据发现没有,事务B中插入这条数据,然后提交事务
- 然后事务A更新这条数据,发现更新成功了
- 事务A中第一次查询查到的版本链为空,第二次查询的时候版本链有数据了
Mysql的锁
并发方案
- 读操作MVCC(并发读), 写操作进行加锁 (读写能够并行操作)
- 读和写都进行加锁 (读写不能并行操作)
- 锁定读: select for update (独占锁) lock in share mode (共享锁) update (排他锁) insert (排他锁/独占锁)、delete (排他锁)
- insert : 一般情况下,新插入一条记录的操作并不加锁,InnoDB通过一种称之为隐式锁来保护这条新插入的记录在本事务提交前不被别的事务访问。当然,在一些特殊情况下INSERT操作也是会获取锁的
- delete: 对一条记录做DELETE操作的过程其实是先在B+树中定位到这条记录的位置,然后获取一下这条记录的X锁,然后再执行delete mark操作。我们也可以把这个定位待删除记录在B+树中位置的过程看成是一个获取X锁的锁定读。
- update:
- 如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发生变化, 看成是一个获取X锁的锁定读。
- 存储空间在修改前后发生变化,先在B+树中定位到这条记录的位置,然后获取一下记录的X锁,将该记录彻底删除掉,最后再插入一条新记录,这个定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读,新插入的记录由INSERT操作提供的隐式锁进行保护。
- 如果修改了该记录的键值,则相当于在原记录上做DELETE操作之后再来一次INSERT操作
锁的种类
- 行锁: 锁一层
- 表锁: 锁整个楼
- 意向锁: 解决效率的问题 (防止遍历数据查看是否有锁)
- 意向共享锁 ,英文名:Intention Shared Lock,简称IS锁。当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。
- 意向独占锁 ,英文名:Intention Exclusive Lock,简称IX锁。当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。
MySQL中的行锁和表锁
- MyISAM: 只支持表锁, 并不支持事务
- 表锁上锁: lock tables teacher read ; lock tables teacher write
- 元数据锁: DDL语句
- AUTO-INC锁: 自增的方式插入, 上了表级别的锁(如果能够确定插入多少数据,会上轻量级锁)
- 行锁 : 索引上加才是行锁,不在索引上就是表锁
- 根据执行计划判断不需要使用行锁,也会使用表锁
- 间隙锁:
- 图中为2~6和 6 ~ 10的记录加了gap锁,意味着不允许别的事务在这条记录前后间隙插入新记录
- 图中为2~6和 6 ~ 10的记录加了gap锁,意味着不允许别的事务在这条记录前后间隙插入新记录
死锁
- 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象
- mysql会自动处理掉死锁
- 查看死锁 show engine innodb status\G
- set global innodb_status_output_locks = ON; 打开死锁的输出
MySQL8新特性
- MySQL 8.0 全内存访问可以轻易跑到 200W QPS,I/O 极端高负载场景跑到 16W QPS
账户与安全
- 到了MySQL8中,用户创建与授权语句必须是分开执行,之前版本是可以一起执行。
- grant all privileges on . to ‘lijin’@‘%’ identified by ‘Lijin@2022’; 之前的创建用户和授权一起执行
- 8.0 创建用户和授权分开
- create user ‘lijin’@‘%’ identified by ‘Lijin@2022’;
- grant all privileges on . to ‘lijin’@‘%’;
- 认证插件更新
- MySQL 8.0中默认的身份认证插件是caching_sha2_password,替代了之前的mysql_native_password。
- show variables like ‘default_authentication%’;
- 可以通过在MySQL的服务端找到my.cnf的文件,把相关参数进行修改
- MySQL 8.0开始允许限制重复使用以前的密码 --(show variables like ‘password%’ 标签:记录,MySQL,笔记,查询,学习,索引,mysql,日志,数据 From: https://blog.csdn.net/weixin_44275684/article/details/141090482