可以使用 BLOB (binary large object),用来存储二进制大对象的字段类型。
TinyBlob 255 值的长度加上用于记录长度的1个字节(8位)
Blob 65K值的长度加上用于记录长度的2个字节(16位)
MediumBlob 16M值的长度加上用于记录长度的3个字节(24位)
LongBlob 4G 值的长度加上用于记录长度的4个字节(32位)。
存:需要高效查询并且文件很小的时候
不存:文件比较大,数据量多或变更频繁的时候
使用utf8mb4
MySQL在5.5.3之后增加了这个utf8mb4的编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode。好在utf8mb4是utf8的超集,除了将编码改为utf8mb4外不需要做其他转换。当然,一般情况下使用utf8也就够了。
可以使用Text存储
TINYTEXT(255长度)
TEXT(65535)
MEDIUMTEXT(int最大值16M)
LONGTEXT(long最大值4G)
BLOB 之前做ERP的时候使用过,互联网项目一般不用BLOB
TEXT 文献,文章,小说类,新闻,会议内容 等
跨时区的业务使用 TIMESTAMP,TIMESTAMP会有时区转换
1、两者的存储方式不一样:
对于TIMESTAMP,它把客户端插入的时间从当前时区转化为UTC(世界标准时间)进行存储。查询时,将其又转化为客户端当前时区进行返回。
而对于DATETIME,不做任何改变,基本上是原样输入和输出。
2、存储字节大小不同
数据类型 | MySQL 5.6.4之前需要存储 | MySQL 5.6.4之后需要存储 |
---|---|---|
DATETIME | 8 bytes | 5 bytes + 小数秒存储 |
TIMESTAMP | 4 bytes | 4 bytes + 小数秒存储 |
分秒数精度 | 存储字节大小 |
---|---|
0 | 0 bytes |
1,2 | 1 bytes |
3,4 | 2 bytes |
5,6 | 3 bytes |
3、两者所能存储的时间范围不一样:
timestamp所能存储的时间范围为:‘1970-01-01 00:00:01.000000’ 到 ‘2038-01-19 03:14:07.999999’。
datetime所能存储的时间范围为:‘1000-01-01 00:00:00.000000’ 到 ‘9999-12-31 23:59:59.999999’。
字符串无法完成数据库内部的范围筛选
在大数据量存储优化索引时,查询必须加上时间范围
int 存储空间小,运算查询效率高,不受时区影响,精度低
timestamp 存储空间小,可以使用数据库内部时间函数比如更新,精度高,需要注意时区转换,timestamp更易读
一般选择timestamp,两者性能差异不明显,本质上存储都是使用的int
1.char的优点是存储空间固定(最大255),没有碎片,尤其更新比较频繁的时候,方便数据文件指针的操作,所以存储读取速度快。缺点是空间冗余,对于数据量大的表,非固定长度属性使用char字段,空间浪费。
2.varchar字段,存储的空间根据存储的内容变化,空间长度为L+size,存储内容长度加描述存储内容长度信息,优点就是空间节约,缺点就是读取和存储时候,需要读取信息计算下标,才能获取完整内容。
第一类:锁包括多线程,数据库,UI展示后超时提交等
第二类:应用与数据库浮点运算精度丢失
float:浮点型,4字节,32bit。
double:双精度实型,8字节,64位
decimal:数字型,128bit,不存在精度损失
对于声明语法DECIMAL(M,D),自变量的值范围如下:
例如字段 salary DECIMAL(5,2),能够存储具有五位数字和两位小数的任何值,因此可以存储在salary列中的值的范围是从-999.99到999.99。
需要不丢失精度的计算使用DECIMAL
仅用于展示没有计算的小数存储可以使用字符串存储
低价值数据允许计算后丢失精度可以使用float double
整型记录不会出现小数的不要使用浮点类型
完整解释:
https://dev.mysql.com/doc/refman/8.0/en/prepare.html
PreparedStatement
子查询虽然很灵活,但是执行效率并不高。
在执行子查询的时候,MYSQL创建了临时表,查询完毕后再删除这些临时表
子查询的速度慢的原因是多了一个创建和销毁临时表的过程。
而join 则不需要创建临时表 所以会比子查询快一点
建议join不超过3张表关联,mysql对内存敏感,关联过多会占用更多内存空间,使性能下降
Too many tables; MySQL can only use 61 tables in a join;
系统限制最多关联61个表
调优:
官方自带:
第三方:性能诊断工具,参数扫描提供建议,参数辅助优化
开启慢查询日志,收集sql
Ø 默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。
Ø 当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。
查看及开启
SHOW VARIABLES LIKE '%slow_query_log%';
默认情况下slow_query_log的值为OFF,表示慢查询日志是禁用的,
开启:set global slow_query_log=1;
只对窗口生效,重启服务失效
慢查询日志记录long_query_time时间
SHOW VARIABLES LIKE '%long_query_time%';
SHOW GLOBAL VARIABLES LIKE 'long_query_time';
全局变量设置,对所有客户端有效。但,必须是设置后进行登录的客户端。
SET GLOBAL long_query_time=0.1;
对当前会话连接立即生效,对其他客户端无效。
SET SESSION long_query_time=0.1; #session可省略
假如运行时间正好等于long_query_time的情况,并不会被记录下来。也就是说,
在mysql源码里是判断大于long_query_time,而非大于等于。
永久生效
slow_query_log =1
slow_query_log_file=/var/lib/mysql/localhost-slow.log
long_query_time=3
log_output=FILE
case
Ø 记录慢SQL并后续分析
SELECT * FROM emp;
SELECT * FROM emp WHERE deptid > 1;
Ø 查询当前系统中有多少条慢查询记录或者直接看慢查询日志
/var/lib/mysql/localhost-slow.log
SHOW GLOBAL STATUS LIKE ‘%Slow_queries%’;
日志分析工具mysqldumpslow
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow。
查看mysqldumpslow的帮助信息
a) mysqldumpslow --help
· -a: 将数字抽象成N,字符串抽象成S
· -s: 是表示按照何种方式排序;
c: 访问次数
l: 锁定时间
r: 返回记录
**t:** **查询时间**
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间
· -t: 即为返回前面多少条的数据;
· -g: 后边搭配一个正则匹配模式,大小写不敏感的;
得到返回记录集最多的10个SQL mysqldumpslow -s r -t 10 /var/lib/mysql/localhost-slow.log 得到访问次数最多的10个SQL mysqldumpslow -s c -t 10 /var/lib/mysql/localhost-slow.log 得到按照时间排序的前10条里面含有左连接的查询语句 mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/localhost-slow.log 另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现爆屏情况 mysqldumpslow -s r -t 10 /var/lib/mysql/localhost-slow.log | more
可以使用EXPLAIN,选择索引过程可以使用 optimizer_trace
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句
,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈
。
用法:
EXPLAIN + SQL语句
数据准备:
USE atguigudb;
CREATE TABLE t1(id INT(10) AUTO_INCREMENT, content VARCHAR(100) NULL, PRIMARY KEY (id));
CREATE TABLE t2(id INT(10) AUTO_INCREMENT, content VARCHAR(100) NULL, PRIMARY KEY (id));
CREATE TABLE t3(id INT(10) AUTO_INCREMENT, content VARCHAR(100) NULL, PRIMARY KEY (id));
CREATE TABLE t4(id INT(10) AUTO_INCREMENT, content1 VARCHAR(100) NULL, content2 VARCHAR(100) NULL, PRIMARY KEY (id));
CREATE INDEX idx_content1 ON t4(content1); -- 普通索引
# 以下新增sql多执行几次,以便演示
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));
INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
INSERT INTO t4(content1, content2) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)), CONCAT('t4_',FLOOR(1+RAND()*1000)));
EXPLAIN SELECT * FROM t1;
注意:
内连接时,MySQL性能优化器会自动判断哪个表是驱动表,哪个表示被驱动表,和书写的顺序无关
EXPLAIN SELECT * FROM t1 INNER JOIN t2;
表示查询中执行select子句或操作表的顺序
EXPLAIN SELECT * FROM t1, t2, t3;
EXPLAIN SELECT t1.id FROM t1 WHERE t1.id =(
SELECT t2.id FROM t2 WHERE t2.id =(
SELECT t3.id FROM t3 WHERE t3.content = 't3_434'
)
);
注意:
查询优化器可能对涉及子查询的语句进行优化,转为连接查询
EXPLAIN SELECT * FROM t1 WHERE content IN (SELECT content FROM t2 WHERE content = 'a');
EXPLAIN SELECT * FROM t1 UNION SELECT * FROM t2;
小结:
查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
EXPLAIN SELECT * FROM t1;
EXPLAIN SELECT * FROM t3 WHERE id = ( SELECT id FROM t2 WHERE content= 'a');
相关子查询(子查询基于外部数据列)
,则子查询就是DEPENDENT SUBQUREY。EXPLAIN SELECT * FROM t3 WHERE id = ( SELECT id FROM t2 WHERE content = t3.content);
EXPLAIN SELECT * FROM t3
WHERE id = ( SELECT id FROM t2 WHERE content = @@character_set_server);
EXPLAIN
SELECT * FROM t3 WHERE id = 1
UNION
SELECT * FROM t2 WHERE id = 1;
EXPLAIN SELECT * FROM t1 WHERE content IN
(
SELECT content FROM t2
UNION
SELECT content FROM t3
);
派生表(子查询在from子句中)
的查询中,MySQL会递归执行这些子查询,把结果放在临时表里。EXPLAIN SELECT * FROM (
SELECT content, COUNT(*) AS c FROM t1 GROUP BY content
) AS derived_t1 WHERE c > 1;
这里的
就是在id为2的查询中产生的派生表。
补充:MySQL在处理带有派生表的语句时,优先尝试把派生表和外层查询进行合并,如果不行,再把派生表物化掉(执行子查询,并把结果放入临时表)
,然后执行查询。下面的例子就是就是将派生表和外层查询进行合并的例子:
EXPLAIN SELECT * FROM (SELECT * FROM t1 WHERE content = 't1_832') AS derived_t1;
如果选择将子查询物化后再与外层查询连接查询
,该子查询的类型就是MATERIALIZED。如下的例子中,查询优化器先将子查询转换成物化表,然后将t1和物化表进行连接查询。 EXPLAIN SELECT * FROM t1 WHERE content IN (SELECT content FROM t2);
代表分区表中的命中情况,非分区表,该项为NULL
说明:
结果值从最好到最坏依次是:
system > const > eq_ref > ref
> fulltext > ref_or_null > index_merge > unique_subquery > index_subquery >range > index > ALL
比较重要的包含:system、const 、eq_ref 、ref、range > index > ALL
SQL 性能优化的目标:至少要达到
range
级别,要求是ref
级别,最好是consts
级别。(阿里巴巴
开发手册要求)
EXPLAIN SELECT * FROM t1;
覆盖索引
,但需要扫描全部的索引记录时覆盖索引:
如果能通过读取索引就可以得到想要的数据,那就不需要读取用户记录,或者不用再做回表操作了。一个索引包含了满足查询结果的数据就叫做覆盖索引。
-- 只需要读取聚簇索引部分的非叶子节点,就可以得到id的值,不需要查询叶子节点
EXPLAIN SELECT id FROM t1;
-- 只需要读取二级索引,就可以在二级索引中获取到想要的数据,不需要再根据叶子节点中的id做回表操作
EXPLAIN SELECT id, deptId FROM t_emp;
EXPLAIN SELECT * FROM t1 WHERE id IN (1, 2, 3);
EXPLAIN SELECT * FROM t_emp WHERE deptId = 1;
EXPLAIN SELECT * FROM t1, t2 WHERE t1.id = t2.id;
主键
或者唯一二级索引
列与常数
进行匹配时EXPLAIN SELECT * FROM t1 WHERE id = 1;
(这是所有type的值中性能最高的场景)
CREATE TABLE t(i int) Engine=MyISAM;
INSERT INTO t VALUES(1);
EXPLAIN SELECT * FROM t;
其他不太常见的类型(了解):
普通索引
来关联子查询,针对包含有IN子查询的查询语句。content1是普通索引字段
EXPLAIN SELECT * FROM t1 WHERE content IN (SELECT content1 FROM t4 WHERE t1.content = t4.content2) OR content = 'a';
唯一索引
来关联子查询。t2的id是主键,也可以理解为唯一的索引字段
EXPLAIN SELECT * FROM t1 WHERE id IN (SELECT id FROM t2 WHERE t1.content = t2.content) OR content = 'a';
多个索引组合使用
,通常出现在有 or 的关键字的sql中。EXPLAIN SELECT * FROM t_emp WHERE deptId = 1 OR id = 1;
EXPLAIN SELECT * FROM t_emp WHERE deptId = 1 OR deptId IS NULL;
一般通过搜索引擎实现,这里我们不展开。
possible_keys
表示执行查询时可能用到的索引,一个或多个。 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
keys
表示实际使用的索引。如果为NULL,则没有使用索引。
EXPLAIN SELECT id FROM t1 WHERE id = 1;
表示索引使用的字节数,根据这个值可以判断索引的使用情况,检查是否充分利用了索引,针对联合索引值越大越好。
如何计算:
-- 创建索引
CREATE INDEX idx_age_name ON t_emp(age, `name`);
-- 测试1
EXPLAIN SELECT * FROM t_emp WHERE age = 30 AND `name` = 'ab%';
-- 测试2
EXPLAIN SELECT * FROM t_emp WHERE age = 30;
显示与key中的索引进行比较的列或常量。
-- ref=atguigudb.t1.id 关联查询时出现,t2表和t1表的哪一列进行关联
EXPLAIN SELECT * FROM t1, t2 WHERE t1.id = t2.id;
-- ref=const 与索引列进行等值比较的东西是啥,const表示一个常数
EXPLAIN SELECT * FROM t_emp WHERE age = 30;
MySQL认为它执行查询时必须检查的行数。值越小越好。
-- 如果是全表扫描,rows的值就是表中数据的估计行数
EXPLAIN SELECT * FROM t_emp WHERE empno = '10001';
-- 如果是使用索引查询,rows的值就是预计扫描索引记录行数
EXPLAIN SELECT * FROM t_emp WHERE deptId = 1;
最后查询出来的数据占所有服务器端检查行数(rows)的百分比
。值越大越好。
-- 先根据二级索引deptId找到数据的主键,有3条记录满足条件,
-- 再根据主键进行回表,最终找到3条记录,有100%的记录满足条件
EXPLAIN SELECT * FROM t_emp WHERE deptId = 1;
-- 这个例子如果name列是索引列则 filtered = 100 否则filtered = 10(全表扫描)
EXPLAIN SELECT * FROM t_emp WHERE `name` = '风清扬';
包含不适合在其他列中显示但十分重要的额外信息。通过这些额外信息来理解MySQL到底将如何执行当前的查询语句
。MySQL提供的额外信息有好几十个,这里只挑介绍比较重要的介绍。
EXPLAIN SELECT * FROM t_emp WHERE 1 != 1;
EXPLAIN SELECT * FROM t_emp WHERE `name` = '风清扬';
EXPLAIN SELECT DISTINCT content FROM t1;
在对查询结果中的记录进行排序时,是可以使用索引的,如下所示:
EXPLAIN SELECT * FROM t1 ORDER BY id;
如果排序操作无法使用到索引,只能在内存中(记录较少时)或者磁盘中(记录较多时)进行排序(filesort),如下所示:
EXPLAIN SELECT * FROM t1 ORDER BY content;
EXPLAIN SELECT id, content1 FROM t4;
EXPLAIN SELECT id FROM t1;
Index Condition Pushdown Optimization (索引下推优化)
如果没有索引下推(ICP)
,那么MySQL在存储引擎层找到满足content1 > 'z'
条件的第一条二级索引记录。主键值进行回表
,返回完整的记录给server层,server层再判断其他的搜索条件是否成立。如果成立则保留该记录,否则跳过该记录,然后向存储引擎层要下一条记录。如果使用了索引下推(ICP
),那么MySQL在存储引擎层找到满足content1 > 'z'
条件的第一条二级索引记录。不着急执行回表
,而是在这条记录上先判断一下所有关于idx_content1
索引中包含的条件是否成立,也就是content1 > 'z' AND content1 LIKE '%a'
是否成立。如果这些条件不成立,则直接跳过该二级索引记录,去找下一条二级索引记录;如果这些条件成立,则执行回表操作,返回完整的记录给server层。-- content1列上有索引idx_content1
EXPLAIN SELECT * FROM t4 WHERE content1 > 'z' AND content1 LIKE '%a';
注意:如果这里的查询条件只有content1 > 'z'
,那么找到满足条件的索引后也会进行一次索引下推的操作,判断content1 > 'z’是否成立(这是源码中为了编程方便做的冗余判断)
EXPLAIN SELECT * FROM t1, t2 WHERE t1.content = t2.content;
下面这个例子就是被驱动表使用了索引:
EXPLAIN SELECT * FROM t_emp, t_dept WHERE t_dept.id = t_emp.deptId;
重点是定位问题。
先
1 使用top观察mysqld的cpu利用率
切换到常用的数据库
使用show full processlist;查看会话
观察是哪些sql消耗了资源,其中重点观察state指标
定位到具体sql
2 pidstat
3 使用show profile观察sql各个阶段耗时
4 服务器上是否运行了其他程序
5 检查一下是否有慢查询
6 pref top
使用pref 工具分析哪些函数引发的cpu过高来追踪定位
垂直分库
一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同 的数据库上面,这样也就将数据或者说压力分担到不同的库上面,如下图:
![](https://img-blog.csdnimg.cn/img_convert/48b4e9dd4a80a6f47a44d61c3cdd52d1.jpeg)
系统被切分成了,用户,订单交易,支付几个模块。
水平分表
把一张表里的内容按照不同的规则 写到不同的库里
相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分 到一个数据库,而另外的某些行又切分到其他的数据库中,如图:
大数据量下可以配合es完成高效查询
经典的问题
视图定义:
1、视图是一个虚表,是从一个或几个基本表(或视图)导出的表。
2、只存放视图的定义,不存放视图对应的数据。
3、基表中的数据发生变化,从视图中查询出的数据也随之改变。
视图的作用:
1、视图能够简化用户的操作
2、视图使用户能以多种角度看待同一数据
3、视图对重构数据库提供了一定程度的逻辑独立性
4、视图能够对机密数据提供安全保护
5、适当的利用视图可以更清晰的表达查询
项目中禁止使用存储过程,存储过程难以调试和扩展,更没有移植性
不得使用外键与级联,一切外键概念必须在应用层解决。
说明:以学生和成绩的关系为例,学生表中的 student_id是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为 级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻 塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
关键的就是state列,mysql列出的状态主要有以下几种:
count()是 SQL92 定义的
标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明:count()会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
select name from user limit 10000,10;
在 使用的时候并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行
通过索引优化的方案:
select name from user where id > 10000 limit 10;
一般大分页情况比较少(很少有人跳转到几百万页去查看数据),实际互联网业务中多数还是按顺序翻页,可以使用缓存提升前几页的查询效率,实际上大多数知名互联网项目也都是这么做的
在阿里巴巴《Java开发手册》中的建议:
【推荐】利用延迟关联或者子查询优化超多分页场景。 说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过 特定阈值的页数进行 SQL 改写。 正例:先快速定位需要获取的 id 段,然后再关联: SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
首先是查看mysql和系统日志来定位错误
最常见的是关闭swap分区后OOM问题:
mysql 分为应用进程和守护进程
当应用进程内存占用过高的时候操作系统可能会kill掉进程,此时守护进程又帮我们重启了应用进程,运行一段时间后又出现OOM如此反复
可以排查以下几个关键点
异常关机或kill -9 mysql 后导致表文件损坏
针对ddl命令,有以下几种方式
copy table 锁原表,创建临时表并拷贝数据
inplace 针对索引修改删除的优化,不需要拷贝所有数据
Online DDL 细分DDL命令来决定是否锁表
可能会锁表,导致无法读写
ORM中的映射失效
索引失效
建议:建个新表,导入数据后重命名
指的是在一台主机上部署多个实例
主要目的是压榨服务器性能
缺点是互相影响