写在前面
Mysql适于OLTP类型的业务, 如交易类(可用mycat分库分表)
Postgresql适于OLTP及OLAP类型的业务, 如分析类(可用citus分库分表)
知识图片:
1.常用SQL语句
>> 条件查询
>> 关联查询
>> 排序查询
>> 分组查询
>> 逻辑关键字
>> 函数
2.数据类型
>> 整数型
>> 浮点数
>> 字符串
>> 二进制
>> 时间
3.引擎
>> MyIASM
>> InnoDB
>> ToKuDB
4.锁
>> 表锁
>> 行级锁
>> 共享锁
5.索引
5.1 类型
>> 唯一索引
>> 主键索引
>> 普通索引
>> 联合索引
>> 全文索引
5.2 实现
>> B-Tree
>> R-Tree
>> Hash
>> FullText
6.存储过程与函数
7.新特性
>> 默认UTF-8编码
>> 隐藏索引
>> 通用表达式
>> 窗口函数
8.SQL优化
>> 查看慢日志
>> explain查看执行计划
>> 优化SQL语句
1.设计思想
1.1 三大设计范式
关系型数据库建议在E-R模型的基础上,
我们需要根据产品的设计策划,抽取出来模型与关系,制定出表结构,
常用的power designer,db desinger等些软件设计数据库表
第1-NF范式
第一范式(1NF):
强调的是列的原子性,即列不能够再分成其他几列
考虑这样一个表:
【联系人】(姓名,性别,电话) 如果在实际场景中,一个联系人有家庭电话和公司电话,那么这种表结构设计就没有达到 1NF。
要符合 1NF 我们只需把列(电话)拆分,即:【联系人】(姓名,性别,家庭电话,公司电话)
第2-NF范式
第二范式(2NF):
首先是 1NF,另外包含两部分内容,
一是表必须有一个主键;
二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。
考虑一个订单明细表:
【OrderDetail】(OrderID,ProductID,UnitPrice,Discount,Quantity,ProductName)。
因为我们知道在一个订单中可以订购多种产品,所以单单一个 OrderID 是不足以成为主键的,主键应该是(OrderID,ProductID)。
显而易见 Discount(折扣),Quantity(数量)完全依赖(取决)于主键(OderID,ProductID),而 UnitPrice,ProductName 只依赖于 ProductID。
所以 OrderDetail 表不符合 2NF。不符合 2NF 的设计容易产生冗余数据。
可以把【OrderDetail】表拆分为:
【OrderDetail】(OrderID,ProductID,Discount,Quantity)和
【Product】(ProductID,UnitPrice,ProductName)
来消除原订单表中UnitPrice,ProductName多次重复的情况。
第3-NF范式
第三范式(3NF):
首先是 2NF,另外非主键列必须直接依赖于主键,不能存在传递依赖。
即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。
考虑一个订单表
【Order】(OrderID,OrderDate,CustomerID,CustomerName,CustomerAddr,CustomerCity)主键是(OrderID)。
其中 OrderDate,CustomerID,CustomerName,CustomerAddr,CustomerCity 等非主键列都完全依赖于主键(OrderID),所以符合 2NF。
不过问题是 CustomerName,CustomerAddr,CustomerCity 直接依赖的是 CustomerID(非主键列),
而不是直接依赖于主键,它是通过传递才依赖于主键,所以不符合 3NF。
通过拆分【Order】为
【Order】(OrderID,OrderDate,CustomerID)和
【Customer】(CustomerID,CustomerName,CustomerAddr,CustomerCity)从而达到 3NF。
第二范式(2NF)和第三范式(3NF)的概念很容易混淆,区分它们的关键点在于,
2NF:非主键列是否完全依赖于主键,还是依赖于主键的一部分;
3NF:非主键列是直接依赖于主键,还是直接依赖于非主键列。
1.2 E-R模型
一对一的关系时, 在哪张表中表示另一方均可
一对多的关系时, 在多的那张表中表示另一方
多对多的关系时, 需要建立一张关联表来实现映射关系
查看MySQL数据表的索引方法(tblname指代表名)
show index from tblname;
#或者
show keys from tblname;
下面是查询结果的相关说明:
· Table 表的名称。
· Non_unique 如果索引不能包括重复词,则为0。如果可以,则为1。
· Key_name 索引的名称。
· Seq_in_index 索引中的列序列号,从1开始。
· Column_name 列名称。
· Collation 列以什么方式存储在索引中。在MySQL中,有值‘A’(升序)或NULL(无分类)。
· Cardinality 索引中唯一值的数目的估计值。通过运行ANALYZE TABLE或myisamchk -a可以更新。基数根据被存储为整数的统计数据来计数,所以即使对于小型表,该值也没有必要是精确的。基数越大,当进行联合时,MySQL使用该索引的机 会就越大。
· Sub_part 如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为NULL。
· Packed 指示关键字如何被压缩。如果没有被压缩,则为NULL。
· Null 如果列含有NULL,则含有YES。如果没有,则该列含有NO。
· Index_type 用过的索引方法(BTREE, FULLTEXT, HASH, RTREE)。
索引的缺点:虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
1.3 mysql的几种存储引擎
可以通过show engines命令查看
功能 | MYISAM | Memory | InnoDB | Archive |
---|---|---|---|---|
存储限制 | 256TB | RAM | 64TB | None |
支持事务 | No | No | Yes | No |
支持全文索引 | Yes | No | No | No |
支持数索引 | Yes | Yes | Yes | No |
支持哈希索引 | No | Yes | No | No |
支持数据缓存 | No | N/A | Yes | No |
支持外键 | No | No | Yes | No |
1.InnoDB(是默认的MySQL引擎):
适用于需提供提交、回滚、崩溃恢复能力的事物安全(ACID兼容)能力,并要求实现并发控制
2.MyISAM
适用于主要用来插入和查询记录的数据库
3.Memory
如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存中的Memory引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果
4.Archive
如果只有INSERT和SELECT操作,可以选择Archive,Archive支持高并发的插入操作,但是本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archive
1.4 mysql的事务与锁机制
1.5 MySQL为啥使用B+树?不用红黑树作为索引的数据结构?
红黑树遵循深度优先, 一般节点较深, 每一层较窄.
B+是B树的变体, 是一种多路树, 遵循广度优先, 一般节点较浅, 每一层较宽.
因为 MySQL 读取数据时, 需要从磁盘IO读到内存中, 每读一层, 都消耗4KB的内存
题外话: 索引里面只存储地址, 不存储具体数据.
为什么 HashMap 用红黑树, 不用B+树?
因为HashMap放在内存中.
红黑树(会左旋,右旋,变色)适合小数据量的存储.
B+树(会分裂)适合大数据量的存储.
2.索引(本文主论btree索引)
2.1mysql索引方法概述
#BTREE
inno db这里是B+TREE, 而非B TREE
btree通常意味着所有的值都是按照顺序存储的,并且每一个叶子页到根的距离相同
btree索引能够加快访问数据的速度,因为存储引擎不再需要进行全表扫描来获取需要的数据,
取而代之的是从索引的根节点开始进行搜索,根节点的槽中存放了指向子节点的指针,
存储引擎根据这些指针向下层查找,通过比较节点页的值和要查找的值可以找到合适的指针进入下一层子节点,
这些指针实际上定义了子节点页中值的上限和下限,最终存储引擎要么是找到对应的值,要么是该记录不存在。
叶子节点比较特别,他们的指针指向的是被索引的数据,而不是其他的节点页(不同的引擎指针类型不同),
其实在根节点与叶子节点之间可能有很多层节点页,树的深度和表的大小直接相关。
B+TREE相关资料
https://www.jianshu.com/p/0aa294c6c37f
#hash
基于哈希表实现,只有精确匹配索引所有列的查询才有效,
对于每一行数据,存储引擎都会对所有的索引列的值计算一个哈希码,
哈希码是一个较小的值,并且不同键值的行计算出来的哈希码不一样,
哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。
在数据库世界里是比较与众不同,如果多个列的哈希值相同,
索引会以链表的方式存放多个记录指针到同一个哈希条目中。
#summary
关系数据库中:
"索引"大多采用"B/B+树"来作为存储结构,
"全文搜索引擎"的索引则主要采用"hash表"的存储结构
hash结构的特点:检索效率非常高,索引的检索可以一次到位,O(1)。
B树需要从根节点到枝节点,最后才能到叶节点进行多次I/O操作,所以hash的效率远远高于B树的效率。
#那么为什么数据库索引还是用B树结构呢?
1、hash索引仅满足“=”、“IN”和“<=>”查询,不能使用范围查询
因为hash索引比较的是经过hash运算之后的hash值,因此只能进行等值的过滤,不能基于范围的查找,
因为经过hash算法处理后的hash值的大小关系,并不能保证与处理前的hash大小关系对应。
2、hash索引无法被用来进行数据的排序操作
由于hash索引中存放的都是经过hash计算之后的值,而hash值的大小关系不一定与hash计算之前的值一样,
所以数据库无法利用hash索引中的值进行排序操作。
3、对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,
所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。
4、Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。
对于选择性比较低的索引键,如果创建 Hash 索引,那么将会存在大量记录指针信息存于同一个 Hash 值相关联。
这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。
(因此:键值重复率低的适合用B树索引)
https://www.cnblogs.com/xiaoboluo768/p/5164342.html
2.2索引分类--按逻辑分
本文未讨论聚簇索引的分法
2.2.1普通索引(Normal)
最基本的索引,没有任何限制
创建索引
CREATE INDEX index_name ON table(column(length))
修改表结构的方式添加索引
ALTER TABLE table_name ADD INDEX index_name ON (column(length))
创建表的时候同时创建索引
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
PRIMARY KEY (`id`),
INDEX index_name (title(length))
)
删除索引
DROP INDEX index_name ON table
2.2.2唯一索引(Unique)
索引列的值必须唯一,但允许有空值
创建唯一索引
CREATE UNIQUE INDEX indexName ON table(column(length))
修改表结构创建唯一索引
ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))
创建表的时候直接指定索引
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
UNIQUE indexName (title(length))
);
2.2.3主键索引(Primary)
主键索引属于一种特殊的唯一索引,不允许有空值
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) NOT NULL ,
PRIMARY KEY (`id`)
);
前三个索引也称为单列索引
2.2.4组合索引(遵循最左前缀集合)
指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。
使用组合索引时遵循最左前缀集合。
ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);
2.2.5全文索引(Full Text)
主要用来查找文本中的关键字,而不是直接与索引中的值相比较。
fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配。
fulltext索引配合match against操作使用,而不是一般的where语句加like。
它可以在create table,alter table ,create index使用,不过目前只有char、varchar,text 列上可以创建全文索引。
值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,
然后再用CREATE index创建fulltext索引,要比先为一张表建立fulltext然后再将数据写入的速度快很多。
创建表的适合添加全文索引
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`content` text CHARACTER NULL ,
PRIMARY KEY (`id`),
FULLTEXT (content)
);
(2)修改表结构添加全文索引
ALTER TABLE article ADD FULLTEXT index_content(content)
(3)直接创建索引
CREATE FULLTEXT INDEX index_content ON article(content)
2.3索引适用/不适用场景
2.3.1 适合使用索引的场景
>> 主键自动创建唯一索引
>> 频繁作为查询条件的字段
>> 查询中与其他表关联的字段
>> 查询中排序的字段
>> 查询中统计或分组字段
2.3.2 不适合使用索引的场景
>> 频繁更新的字段
>> where 条件中用不到的字段
>> 表记录太少
>> 经常增删改的表
>> 字段的值的差异性不大或重复性高
2.3.3 索引创建和使用原则
>> 单表查询:哪个列作查询条件,就在该列创建索引
>> 多表查询:left join 时,索引添加到右表关联字段;right join 时,索引添加到左表关联字段
>> 不要对索引列进行任何操作(计算、函数、类型转换)
>> 索引列中不要使用 !=,<> 非等于索引列不要为空,且不要使用 is null 或 is not null 判断
>> 索引字段是字符串类型,查询条件的值要加''单引号,避免底层类型自动转换
#违背上述原则可能会导致索引失效,具体情况需要使用 explain 命令进行查看
2.3.4 索引失效情况
#除了违背索引创建和使用原则外,如下情况也会导致索引失效:
模糊查询时,以 % 开头使用 or 时,如:字段1(非索引)or 字段2(索引)会导致索引失效。
使用复合索引时,不使用第一个索引列, 会导致索引失效。
2.4索引不足之处
(1)索引提高了查询的速度,但是降低了INSERT、UPDATE、DELETE的速度,
因为在插入、修改、删除数据时,还要同时操作一下索引文件;
(2)建立索引会占用一定的磁盘空间。
3. --->58到家30条设计军规(转) (本文有修改)
军规适用场景:并发量大、数据量大的互联网业务
军规:介绍内容
解读:讲解原因,解读比军规更重要
一、基础规范
1.必须使用InnoDB存储引擎
解读:支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高
2.必须使用UTF8字符集
解读:万国码,无需转码,无乱码风险,节省空间
3.数据表、数据字段必须加入中文注释
解读:N年后谁tm知道这个r1,r2,r3字段是干嘛的
4.禁止使用存储过程、视图、触发器、Event
解读:
高并发大数据的互联网业务,架构设计思路是“解放数据库CPU,将计算转移到服务层”,
并发量大的情况下,这些功能很可能将数据库拖死,
业务逻辑放到服务层具备更好的扩展性,能够轻易实现“增机器就加性能”。
数据库擅长存储与索引,CPU计算还是上移吧。
5.禁止存储大文件或者大照片
解读:为何要让数据库做它不擅长的事情?大文件和照片存储在文件系统,数据库里存URI多好。
二、命名规范
1.只允许使用内网域名,而不是ip连接数据库
2.线上环境、开发环境、测试环境数据库内网域名遵循命名规范
业务名称:xxx
线上环境:dj.xxx.db
开发环境:dj.xxx.rdb
测试环境:dj.xxx.tdb
从库在名称后加-s标识,备库在名称后加-ss标识
线上从库:dj.xxx-s.db
线上备库:dj.xxx-sss.db
3.库名、表名、字段名:小写,下划线风格,不超过32个字符,必须见名知意,禁止拼音英文混用
4.表名t_xxx,非唯一索引名idx_xxx,唯一索引名uniq_xxx
三、表设计规范
1.单实例表数目必须小于500
2.单表列数目必须小于30
3.表必须有主键,例如自增主键
解读:
a)主键递增,数据行写入可以提高插入性能,可以避免page分裂,减少表碎片提升空间和内存的使用
b)主键要选择较短的数据类型, Innodb引擎普通索引都会保存主键的值,
较短的数据类型可以有效的减少索引的磁盘空间,提高索引的缓存效率
c) 无主键的表删除,在row模式的主从架构,会导致备库夯住
4.禁止使用外键,如果有外键完整性约束,需要应用程序控制
解读:
外键会导致表与表之间耦合,update与delete操作都会涉及相关联的表,十分影响sql 的性能,甚至会造成死锁。
高并发情况下容易造成数据库性能,大数据高并发业务场景数据库使用以性能优先。
5.所有表必须使用Innodb存储引擎
没有特殊要求(即Innodb无法满足的功能如:列存储,存储空间数据等)的情况下,
所有表必须使用Innodb存储引擎(mysql5.5之前默认使用Myisam,5.6以后默认的为Innodb)。
Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能更好。
6.数据库和表的字符集统一使用UTF8
兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,
不同的字符集进行比较前需要进行转换会造成索引失效,
如果数据库中有存储emoji表情的需要,字符集需要采用utf8mb4字符集。
7.所有表和字段都需要添加注释
使用comment从句添加表和列的备注,从一开始就进行数据字典的维护
8.单表最大500W数据, 最多50个字段, 单库最大500G
500万并不是Mysql数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题。
可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小
9.谨慎使用Mysql分区表
分区表在物理上表现为多个文件,在逻辑上表现为一个表;
谨慎选择分区键,跨分区查询效率可能更低;
建议采用物理分表的方式管理大数据。
10.尽量做到冷热数据分离,减小表的宽度
Mysql限制每个表最多存储4096列,并且每一行数据的大小不能超过65535字节。
减少磁盘IO,保证热数据的内存缓存命中率
(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的IO);
更有效的利用缓存,避免读入无用的冷数据;
经常一起使用的列放到一个表中(避免更多的关联操作)。
11.禁止在表中建立预留字段
预留字段的命名很难做到见名识义。
预留字段无法确认存储的数据类型,所以无法选择合适的类型。
对预留字段类型的修改,会对表进行锁定。
12.禁止在数据库中存储图片,文件等大的二进制数据
通常文件很大,会短时间内造成数据量快速增长,数据库进行数据库读取时,
通常会进行大量的随机IO操作,文件很大时,IO操作很耗时。
通常存储于文件服务器,数据库只存储文件地址信息
13.禁止在线上做数据库压力测试
14.禁止从开发环境,测试环境直接连接生成环境数据库
四、字段设计规范
1.必须把字段定义为NOT NULL并且提供默认值
解读:
a)null的列使索引/索引统计/值比较都更加复杂,对MySQL来说更难优化
b)null 这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;
同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多
c)null值需要更多的存储空,无论是表还是索引中每行中的null的列都需要额外的空间来标识
d)对null 的处理时候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in这些操作符号。
如:where name!=’shenjian’,如果存在name为null值的记录,查询结果就不会包含name为null值的记录
2.禁止使用TEXT、BLOB类型
解读:
会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能
3.禁止使用小数存储货币
解读:使用整数吧,小数容易导致钱对不上
4.必须使用varchar(20)存储手机号
解读:
a)涉及到区号或者国家代号,可能出现+-()
b)手机号会去做数学运算么?
c)varchar可以支持模糊查询,例如:like“138%”
5.禁止使用ENUM,可使用TINYINT代替
解读:
a)增加新的ENUM值要做DDL操作
b)ENUM的内部实际存储就是整数,你以为自己定义的是字符串?
6.将字符串转换成数字类型存储,如:将IP地址转换成整形数据
mysql提供了两个方法来处理ip地址
1) inet_aton 把ip转为无符号整型(4-8位)
2) inet_ntoa 把整型的ip转为地址
插入数据前,先用inet_aton把ip地址转为整型,可以节省空间,
显示数据时,使用inet_ntoa把整型的ip地址转为地址显示即可。
7.对于非负型的数据(如自增ID、整型IP)来说,要优先使用无符号整型来存储
原因:无符号相对于有符号可以多出一倍的存储空间
1) SIGNED INT -2147483648~2147483647
2) UNSIGNED INT 0~4294967295
3) VARCHAR(N)中的N代表的是字符数,而不是字节数,使用UTF8存储255个汉字 Varchar(255)=765个字节。
过大的长度会消耗更多的内存。
8.避免使用TEXT、BLOB数据类型,最常见的TEXT类型可以存储64k的数据
1) 建议把BLOB或是TEXT列分离到单独的扩展表中
Mysql内存临时表不支持TEXT、BLOB这样的大数据类型,
如果查询中包含这样的数据,在排序等操作时,就不能使用内存临时表,必须使用磁盘临时表进行。
而且对于这种数据,Mysql还是要进行二次查询,会使sql性能变得很差,但是不是说一定不能使用这样的数据类型。
如果一定要使用,建议把BLOB或是TEXT列分离到单独的扩展表中,
查询时一定不要使用select * 而只需要取出必要的列,不需要TEXT列的数据时不要对该列进行查询。
2) TEXT或BLOB类型只能使用前缀索引
因为MySQL对索引字段长度是有限制的,所以TEXT类型只能使用前缀索引,并且TEXT列上是不能有默认值的
10.使用TIMESTAMP(4个字节)或DATETIME类型(8个字节)存储时间
TIMESTAMP 存储的时间范围 1970-01-01 00:00:01 ~ 2038-01-19-03:14:07
TIMESTAMP 占用4字节和INT相同,但比INT可读性高
超出TIMESTAMP取值范围的使用DATETIME类型存储
经常会有人用字符串存储日期型的数据(不正确的做法)
缺点1:无法用日期函数进行计算和比较
缺点2:用字符串存储日期要占用更多的空间
11.同财务相关的金额类数据必须使用decimal类型
非精准浮点:float,double
精准浮点:decimal
Decimal类型为精准浮点数,在计算时不会丢失精度
占用空间由定义的宽度决定,每4个字节可以存储9位数字,并且小数点要占用一个字节
可用于存储比bigint更大的整型数据
五、索引设计规范
1.单表索引建议控制在5个以内
2.单索引字段数不允许超过5个
解读:字段超过5个时,实际已经起不到有效过滤数据的作用了
3.禁止在更新十分频繁、区分度不高的属性上建立索引
解读:
a)更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能
b)“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,
性能与全表扫描类似, 即不在低识别度的字段上建立索引, 不在索引上使用函数
4.建立组合索引,必须把区分度高的字段放在前面
解读:能够更加有效的过滤数据
5.每个Innodb表必须有个主键
Innodb是一种索引组织表:数据的存储的逻辑顺序和索引的顺序是相同的。
每个表都可以有多个索引,但是表的存储顺序只能有一种。
Innodb是按照主键索引的顺序来组织表的
不要使用更新频繁的列作为主键,不适用多列主键(相当于联合索引)
不要使用UUID,MD5,HASH,字符串列作为主键(无法保证数据的顺序增长)
主键建议使用自增ID值
6.常见索引列建议
1) 出现在SELECT、UPDATE、DELETE语句的WHERE从句中的列
2) 包含在ORDER BY、GROUP BY、DISTINCT中的字段
3) 并不要将符合1和2中的字段的列都建立一个索引, 通常将1、2中的字段建立联合索引效果更好
4) 多表join的关联列
7.如何选择索引列的顺序
建立索引的目的是:
希望通过索引进行数据查找,减少随机IO,增加查询性能 ,
索引能过滤出越少的数据,则从磁盘中读入的数据也就越少。
1) 区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数)
2) 尽量把字段长度小的列放在联合索引的最左侧
(因为字段长度越小,一页能存储的数据量越大,IO性能也就越好)
3) 使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引)
8.尽量避免使用外键约束
1) 不建议使用外键约束(foreign key),但一定要在表与表之间的关联键上建立索引
2) 外键可用于保证数据的参照完整性,但建议在业务端实现
3) 外键会影响父表和子表的写操作从而降低性能
六、SQL使用规范
1.禁止使用SELECT *,只获取必要的字段,需要显示说明列属性
解读:
a)读取不需要的列会增加CPU、IO、NET消耗
b)不能有效的利用覆盖索引
c)使用SELECT *容易在增加或者删除字段后出现程序BUG
2.禁止使用INSERT INTO t_xxx VALUES(xxx),必须显示指定插入的列属性
解读:容易在增加或者删除字段后出现程序BUG
3.禁止使用属性隐式转换
解读:
SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描,而不能命中phone索引,猜猜为什么?(这个线上问题不止出现过一次)
4.禁止在WHERE条件的属性上使用函数或者表达式
解读:
SELECT uid FROM t_user WHERE from_unixtime(day)>='2017-02-15' 会导致全表扫描
正确的写法是:
SELECT uid FROM t_user WHERE day>= unix_timestamp('2017-02-15 00:00:00')
5.禁止负向查询,以及%开头的模糊查询
解读:
a)负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描
b)%开头的模糊查询,会导致全表扫描
6.禁止大表使用JOIN查询,禁止大表使用子查询, 避免使用JOIN关联太多的表
解读:会产生临时表,消耗较多内存与CPU,极大影响数据库性能
7.禁止使用OR条件,必须改为IN查询
解读:
旧版本Mysql的OR查询是不能命中索引的,即使能命中索引,为何要让数据库耗费更多的CPU帮助实施查询优化呢?
8.应用程序必须捕获SQL异常,并有相应处理
总结:大数据量高并发的互联网业务,极大影响数据库性能的都不让用,不让用哟。
9.druid的监控功能, 可以辅助使用
10.减少同数据库的交互次数
数据库更适合处理批量操作,合并多个相同的操作到一起,可以提高处理效率。
11.WHERE从句中禁止对列进行函数转换和计算
对列进行函数转换或计算时会导致无法使用索引
1) 不推荐:
where date(create_time)='20190101'
2) 推荐:
where create_time >= '20190101' and create_time < '20190102'
12.拆分复杂的大SQL为多个小SQL
大SQL逻辑上比较复杂,需要占用大量CPU进行计算的SQL
MySQL中,一个SQL只能使用一个CPU进行计算
SQL拆分后可以通过并行执行来提高处理效率
# 补充说明:
(32)
(33)在where, order by, group by的字段上加上合适的索引
4.DB性能优化思路
4.1问题定位
# 1.关键分析指标(sql执行计划)
在sql语句前, 加上explain即可进行分析
# 2.慢日志记录(大于0.5s的sql可设为慢查询)
slow_query_log=1
slow-query-log-file=/mysql/data/mysql_slow.log # 自定义即可
long_query_time=0.5
4.1.1explain执行计划查看
在sql语句前, 加上explain即可进行分析
#字段解释:
1. id:select 查询序列号。
id相同,执行顺序由上至下;id不同,id值越大优先级越高,越先被执行
2. select_type:查询数据的操作类型,其值如下:
>> simple:简单查询,不包含子查询或
>> unionprimary:包含复杂的子查询,最外层查询标记为该值
>> subquery:在 select 或 where 包含子查询,被标记为该值
>> derived:在 from 列表中包含的子查询被标记为该值,
MySQL 会递归执行这些子查询,把结果放在临时表
>> union:若第二个 select 出现在 union 之后,则被标记为该值。
若 union 包含在 from 的子查询中,外层 select 被标记为 derived
>> union result:从 union 表获取结果的 select
3. table:显示该行数据是关于哪张表
4. partitions:匹配的分区
5. type:表的连接类型,其值,性能由高到底排列如下:
>> system:表只有一行记录,相当于系统表
>> const:通过索引一次就找到,只匹配一行数据
>> eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。
常用于主键或唯一索引扫描
>> ref:非唯一性索引扫描,返回匹配某个单独值的所有行。
用于=、< 或 > 操作符带索引的列
>> range:只检索给定范围的行,使用一个索引来选择行。
一般使用between、>、<情况
>> index:只遍历索引树
>> ALL:全表扫描,性能最差注:
"前5种情况都是理想情况的索引使用情况。通常优化至少到range级别,最好能优化到 ref"
6. possible_keys:指出 MySQL 使用哪个索引在该表找到行记录。
如果该值为 NULL,说明没有使用索引,可以建立索引提高性能
7. key:显示 MySQL 实际使用的索引。
如果为 NULL,则没有使用索引查询
8. key_len:表示索引中使用的字节数
通过该列计算查询中使用的索引的长度。
在不损失精确性的情况下,长度越短越好 显示的是索引字段的最大长度,并非实际使用长度
9. ref:显示该表的索引字段关联了哪张表的哪个字段
10. rows:
根据表统计信息及选用情况,大致估算出找到所需的记录或所需读取的行数,数值越小越好
11. filtered:返回结果的行数占读取行数的百分比
值越大越好
12. extra: 包含不合适在其他列中显示但十分重要的额外信息,常见的值如下:
>> using filesort:说明 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。
>> temporary:使用了临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。
常见于排序 order by 和分组查询 group by。
>> using index:表示相应的 select 操作使用了覆盖索引,避免了访问表的数据行,效率不错
>> using where:where 子句用于限制哪一行
>> using join buffer:使用连接缓存
>> distinct:发现第一个匹配后,停止为当前的行组合搜索更多的行
"注意:出现前 2 个值,SQL 语句必须要优化。"
4.1.2开启慢查询日志
#在配置文件 "my.cnf" 中的 [mysqld] 一行下边添加参数:
slow_query_log = 1 #表示开启慢查询
slow_query_log_file=/var/lib/mysql/slow-query.log # 表示慢查询日志存放的位置
long_query_time = 2 # 表示查询 >=2 秒才记录日志
log_queries_not_using_indexes = 1 # 记录没有使用索引的 SQL 语句
注意:slow_query_log_file 的路径不能随便写,否则 MySQL 服务器可能没有权限将日志文件写到指定的目录中。
修改保存文件后,重启 MySQL 服务。
在 /var/lib/mysql/ 目录下会创建 slow-query.log 日志文件。
连接 MySQL 服务端执行如下命令可以查看配置情况。
show variables like 'slow_query%';
show variables like 'long_query_time';
4.2整体思路
1.建立索引(多表时,建立组合索引)
2.在应用服务器资源充足时,排序,分页尽量放在业务代码中(采用jdk8的stream api)
3.limit优化
4.排序加limit时,需要再加上一个主键的排序(以防出现查询异常)
5.添加redis缓存,设计合理的key及过期时间,提高缓存命中率(可以通过打印日志,来记录是否命中)
4.3limit优化
MySQL的limit工作原理就是先读取n条记录,
然后抛弃前n条,
读m条想要的,
所以n越大,性能会越差。
优化前SQL
SELECT * FROM member ORDER BY last_active LIMIT 50,5
优化后SQL
SELECT * FROM member INNER JOIN
(SELECT member_id FROM member ORDER BY last_active LIMIT 50, 5)
USING (member_id)
区别在于,优化前的SQL需要更多I/O浪费,因为先读索引,再读数据,然后抛弃无需的行。
而优化后的SQL(子查询那条)只读索引(Cover index)就可以了,然后通过member_id读取需要的列。
参考资源
https://www.jianshu.com/p/0c01a46bff7e
https://cloud.tencent.com/developer/article/1150990
https://mp.weixin.qq.com/s/HFLgDz163FhB_3BosL_kCQ (MySQL设计规范)