赞
踩
MySQL中索引分三类:B+树索引、Hash索引、全文索引
MyISAM的索引方式都是非聚簇的,与InnoDB包含1个聚簇索引是不同的。
在InnoDB存储引擎中,我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录,而在MyISAM中却需要进行一次回表操作,意味着MyISAM中建立的索引相当于全部都是二级索引 。
InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是分离的 ,索引文件仅保存数据记录的地址。
*.sdi(描述表结构)
、*.MYD(数据)
,*.MYI(索引)
.ibd(表结构、索引和数据都存在一起)
InnoDB的非聚簇索引data域存储相应记录主键的值 ,而MyISAM索引记录的是地址 。换句话说,InnoDB的所有非聚簇索引都引用主键作为data域。
MyISAM的回表操作是十分快速的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。
InnoDB要求表必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。
总结:
会
如果有主键会创建聚簇索引
如果没有主键会生成rowid作为隐式主键
目的是为了帮我们合理的存储这些数据,使这些数据能够更好的在B+树中存储。
假设有一个表index_demo,表中有2个INT类型的列,1个CHAR(1)类型的列,c1列为主键:
CREATE TABLE index_demo(c1 INT,c2 INT,c3 CHAR(1),PRIMARY KEY(c1)) ;
index_demo表的简化的行格式示意图如下:
我们只在示意图里展示记录的这几个部分:
record_type:
表示记录的类型, 0是普通记录、 2是最小记录、 3 是最大记录、1是B+树非叶子节点记录。next_record:
表示下一条记录的相对位置,我们用箭头来表明下一条记录。各个列的值:
这里只记录在 index_demo 表中的三个列,分别是 c1 、 c2 和 c3 。其他信息:
除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息。将其他信息
项暂时去掉并把它竖起来的效果就是这样:
把一些记录放到页里的示意图就是(这里一页就是一个磁盘块,代表一次IO):
MySQL InnoDB的默认的页大小是16KB
,因此数据存储在磁盘中,可能会占用多个数据页。如果各个页中的记录没有规律,我们就不得不依次遍历所有的数据页。如果我们想快速的定位到需要查找的记录在哪些数据页中
,我们可以这样做 :
以页28
为例,它对应目录项2
,这个目录项中包含着该页的页号28
以及该页中用户记录的最小主键值 5
。我们只需要把几个目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键值快速查找某条记录的功能了。比如:查找主键值为 20 的记录,具体查找过程分两步:
主键值为20的记录在目录项3中
(因为 12 ≤ 20 < 209 ),对应页9
。至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为索引
。
我们新分配一个编号为30的页来专门存储目录项记录
,页10、28、9、20专门存储用户记录
:
目录项记录和普通的用户记录的不同点:
现在查找主键值为 20 的记录,具体查找过程分两步:
更复杂的情况如下:
我们生成了一个存储更高级目录项的 页33 ,这个页中的两条记录分别代表页30和页32,如果用户记录的主键值在 [1, 320)
之间,则到页30中查找更详细的目录项记录,如果主键值 不小于320 的话,就到页32中查找更详细的目录项记录。这个数据结构,它的名称是 B+树 。
特点:
索引和数据保存在同一个B+树中
页内的记录
是按照主键
的大小顺序排成一个单向链表
。
页和页之间
也是根据页中记录的主键
的大小顺序排成一个双向链表
。
非叶子节点存储的是记录的主键+页号
。
叶子节点存储的是完整的用户记录
。
优点:
索引和数据保存在同一个B+树中
,因此从聚簇索引中获取数据比非聚簇索引更快。排序查找
和范围查找
速度非常快。数据都是紧密相连
,数据库可以从更少的数据块中提取数据,节省了大量的IO操作
。缺点:
自增的ID列为主键
。主键为不可更新
。限制:
MyISAM不支持聚簇索引
。每个MySQL的表只能有一个聚簇索引
。非空的唯一索引列代替
。如果没有这样的列,InnoDB会隐式的定义一个主键
作为聚簇索引。主键应选择有序的id
,不建议使用无序的id,比如UUID、MD5、HASH、字符串作为主键,无法保证数据的顺序增长。(二级索引、辅助索引)
聚簇索引
,只能在搜索条件是主键值
时才发挥作用,因为B+树中的数据都是按照主键进行排序的,如果我们想以别的列作为搜索条件,那么需要创建非聚簇索引
。
例如,以c2列作为搜索条件
,那么需要使用c2列创建一棵B+树
,如下所示:
这个B+树与聚簇索引有几处不同:
页内的记录
是按照从c2列
的大小顺序排成一个单向链表
。
页和页之间
也是根据页中记录的c2列
的大小顺序排成一个双向链表
。
非叶子节点存储的是记录的c2列+页号
。
叶子节点存储的并不是完整的用户记录,而只是c2列+主键
这两个列的值。
一张表可以有多个非聚簇索引:
**例如:**根据c2列的值查找c2=4的记录,查找过程如下:
根页面44
定位到页42
(因为2 ≤ 4 < 9
)c2列没有唯一性约束
,所以c2=4的记录可能分布在多个数据页中,又因为 2 ≤ 4 ≤ 4
,所以确定实际存储用户记录的页在页34和页35
中。定位到具体的记录
。只存储了c2和c1(主键)
两个列,所以我们必须再根据主键值去聚簇索引中再查找
一遍完整的用户记录。平衡二叉树
平衡二叉树(AVL)
AVL树全称G.M. Adelson-Velsky和E.M. Landis,这是两个人的人名。
平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树, 可以保证查询效率较高。
具有以下特点:
AVL的生成演示:https://www.cs.usfca.edu/~galles/visualization/AVLtree.html
AVL的问题
众所周知,IO操作的效率很低,在大量数据存储中,查询时我们不能一下子将所有数据加载到内存中,只能逐节点加载(一个节点一次IO)。如果我们利用二叉树作为索引结构,那么磁盘的IO次数和索引树的高度是相关的
。平衡二叉树由于树深度过大而造成磁盘IO读写过于频繁,进而导致效率低下。
为了提高查询效率,就需要 减少磁盘IO数 。为了减少磁盘IO的次数,就需要尽量降低树的高度
,需要把原来“瘦高”的树结构变的“矮胖”,树的每层的分叉越多越好。针对同样的数据,如果我们把二叉树改成 三叉树:
上面的例子中,我们将二叉树变成了三叉树,降低了树的高度。如果能够在一个节点中存放更多的数据,我们还可以进一步减少节点的数量,从而进一步降低树的高度。这就是多叉树
。
普通树的问题
解决方案:平衡二叉树(AVL)
红黑树
在这个棵严格的平台树上又进化为“红黑树”{是一个非严格的平衡树 左子树与右子树的高度差不能超过1},红黑树的长子树只要不超过短子树的两倍即可!
当再次插入7的时候,这棵树就会发生旋转
B+ 树和 B 树的差异:
真实环境
中一个页存放的记录数量是非常大的(默认16KB),假设指针与键值忽略不计(或看做10个字节),数据占 1 kb 的空间:1600×16=25600
条记录。1600×1600×16=40960000
条记录。B+树的非叶子节点不存储用户记录,只存储目录记录,相对B树每个节点可以存储更多的记录,树的高度会更矮胖,IO次数也会更少。
自适应哈希索引是Innodb引擎的一个特殊功能,当它注意到某些索引值被使用的非常频繁时,会在内存中基于B-Tree所有之上再创建一个哈希索引,这就让B-Tree索引也具有哈希索引的一些优点,比如快速哈希查找。这是一个完全自动的内部行为,用户无法控制或配置。
使用命令
SHOW ENGINE INNODB STATUS \G ;
查看INSERT BUFFER AND ADAPTIVE HASH INDEX
多叉树(multiway tree)允许每个节点可以有更多的数据项和更多的子节点
。2-3树,2-3-4树就是多叉树,多叉树通过重新组织节点,减少节点数量,增加分叉,减少树的高度
,能对二叉树进行优化。
2-3树
下面2-3树就是一颗多叉树
2-3树具有如下特点:
2-3-4树
字符串无法完成以上操作
删除之后
优点
聚簇(主键)索引:
非聚簇索引:
索引的代价
索引是个好东西,可不能乱建,它在空间和时间上都会有消耗:
每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页,一个页默认会占用 16KB 的存储空间
,一棵很大的B+树由许多数据页组成,那就是很大的一片存储空间。
每次对表中的数据进行 增、删、改 操作时,都需要去修改各个B+树索引
。而增、删、改操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行一些记录移位、页面分裂、页面回收等操作来维护好节点和记录的排序。
如果我们建了许多索引,每个索引对应的B+树都要进行相关的维护操作,会给性能拖后腿。
B 树和 B+ 树都可以作为索引的数据结构,在 MySQL 中采用的是 B+ 树。
但B树和B+树各有自己的应用场景,不能说B+树完全比B树好,反之亦然。
不一定
B 树和 B+ 树都可以作为 索引的数据结构,**在 MySQL 中采用的是 B+ 树。**
第一种方式是分表存储,然后创建索引
第二是使用es为大文本创建索引
聚簇索引数据和索引存放在一起组成一个b+树
参考005题
聚簇索引只能有一个
非聚簇索引可以有多个
参考005题
因为聚簇索引中有时会引发分页操作、重排操作数据有可能会移动
id age name sex
age -> index
select * from user where age >20 ;
第一次 取回id,第二次(回表)根据id拿到完整数据
select * from user where age >20 ;
id age name sex
age -> index
select * from user where age >20 ;
第一次 取回id,第二次(回表)根据id拿到完整数据
age,name -> index
select age from user where age >20 and name like"张%" ;
覆盖索引不会回表查询,查询效率也是比较高的
不一定,只要b+树中包含的字段(创建索引的字段),覆盖(包含)想要select 的字段,那么就不会回表查询了。
为了控制非聚簇索引的大小
不是,InnoDB会生成rowid辅助回表查询
为c2和c3列建立联合索引,
如下所示:
c2,c3 - > index
c3,c2 -> index
where c3=?
全职匹配
最左前缀
我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按照 c2和c3列
的大小进行排序,这个包含两层含义:
c2
列进行排序。c2
列相同的情况下,采用c3
列进行排序c2列、c3列和主键c1列组成
create index idx_c2_c3 on user (c2,c3);
CREATE TABLE customer (
id INT UNSIGNED AUTO_INCREMENT,
customer_no VARCHAR(200),
customer_name VARCHAR(200),
PRIMARY KEY(id), -- 主键索引:列设定为主键后会自动建立索引,唯一且不能为空。
UNIQUE INDEX uk_no (customer_no), -- 唯一索引:索引列值必须唯一,允许有NULL值,且NULL可能会出现多次。
KEY idx_name (customer_name), -- 普通索引:既不是主键,列值也不需要唯一,单纯的为了提高查询速度而创建。
KEY idx_no_name (customer_no,customer_name) -- 复合索引:即一个索引包含多个列。
);
CREATE TABLE customer1 (
id INT UNSIGNED,
customer_no VARCHAR(200),
customer_name VARCHAR(200)
);
ALTER TABLE customer1 ADD PRIMARY KEY customer1(id); -- 主键索引
CREATE UNIQUE INDEX uk_no ON customer1(customer_no); -- 唯一索引
CREATE INDEX idx_name ON customer1(customer_name); -- 普通索引
CREATE INDEX idx_no_name ON customer1(customer_no,customer_name); -- 复合索引
是
业务需求唯一字段的时候,一般不考虑性能问题
. 【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。 说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明 显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必 然有脏数据产生。
适合创建索引
频繁作为where条件语句查询字段
关联字段需要建立索引
排序字段可以建立索引
分组字段可以建立索引(因为分组前提是排序)
统计字段可以建立索引(如.count(),max())
不适合创建索引
频繁更新的字段不适合建立索引
where,分组,排序中用不到的字段不必要建立索引
可以确定表数据非常少不需要建立索引
参与mysql函数计算的列不适合建索引
创建索引时避免有如下极端误解:
1)宁滥勿缺。认为一个查询就需要建一个索引。
2)宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。
3)抵制惟一索引。认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。
5.6之前的版本是没有索引下推这个优化的
**Using index condition:**叫作 Index Condition Pushdown Optimization (索引下推优化)
如果没有索引下推(ICP)
,那么MySQL在存储引擎层找到满足content1 > 'z'
条件的第一条二级索引记录。主键值进行回表
,返回完整的记录给server层,server层再判断其他的搜索条件是否成立。如果成立则保留该记录,否则跳过该记录,然后向存储引擎层要下一条记录。如果使用了索引下推(ICP
),那么MySQL在存储引擎层找到满足content1 > 'z'
条件的第一条二级索引记录。不着急执行回表
,而是在这条记录上先判断一下所有关于idx_content1
索引中包含的条件是否成立,也就是content1 > 'z' AND content1 LIKE '%a'
是否成立。如果这些条件不成立,则直接跳过该二级索引记录,去找下一条二级索引记录;如果这些条件成立,则执行回表操作,返回完整的记录给server层。总结:
未开启索引下推:
开启索引下推:在条件查询时,当前索引树如果满足全部筛选条件,可以在当前树中完成全部筛选过滤,得到比较小的结果集再进行回表操作
-- 显示查询分析
EXPLAIN SELECT * FROM emp WHERE emp.name LIKE 'abc%';
EXPLAIN SELECT * FROM emp WHERE LEFT(emp.name,3) = 'abc'; --索引失效
拓展:Alibaba《Java开发手册》
【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
EXPLAIN SELECT * FROM emp WHERE name LIKE '%ab%'; --索引失效
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name = 'abc' ;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name <> 'abc' ; --索引失效
EXPLAIN SELECT * FROM emp WHERE emp.name IS NULL;
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL; --索引失效
**注意:**当数据库中的数据的索引列的NULL值达到比较高的比例的时候
,即使在IS NOT NULL 的情况下 MySQL的查询优化器会选择使用索引,此时type的值是range(范围查询)
-- 将 id>20000 的数据的 name 值改为 NULL
UPDATE emp SET `name` = NULL WHERE `id` > 20000;
-- 执行查询分析,可以发现 IS NOT NULL 使用了索引
-- 具体多少条记录的值为NULL可以使索引在IS NOT NULL的情况下生效,由查询优化器的算法决定
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL
EXPLAIN SELECT * FROM emp WHERE name='123';
EXPLAIN SELECT * FROM emp WHERE name= 123; --索引失效
id,name,age
name 创建索引
select * from user where name like ‘%明’
type=all
select name,id from user where name like ‘%明’
type=index
张明
(name,age)
其实并不会完全失效,覆盖索引下会出现type=index,表示遍历了索引树,再回表查询,
覆盖索引没有生效的时会直接type=all
没有高效使用索引是因为字符串索引会逐个转换成accii码,生成b+树时按首个字符串顺序排序,类似复合索引未用左列字段失效一样,跳过开始部分也就无法使用生成的b+树了
不可用手动直接干预,只能通过mysql优化器自动选择
show index from t_emp; // 显示表上的索引
explain select * from t_emp where id=1; // 显示可能会用到的索引及最终使用的索引
set session optimizer_trace="enabled=on",end_markers_in_json=on;
SELECT * FROM information_schema.OPTIMIZER_TRACE;
set session optimizer_trace="enabled=off";
一般性建议
Ø 对于单键索引,尽量选择过滤性更好的索引(例如:手机号,邮件,身份证)
Ø 在选择组合索引的时候,过滤性最好的字段在索引字段顺序中,位置越靠前越好。
Ø 选择组合索引时,尽量包含where中更多字段的索引
Ø 组合索引出现范围查询时,尽量把这个字段放在索引次序的最后面
Ø 尽量避免造成索引失效的情况
没有过滤条件不走索引
select name,id from user where name like ‘%明’ order by name;
select name,id,age from user where name like ‘%明’
关键配置:
单路排序:一次取出所有字段进行排序,内存不够用的时候会使用磁盘
双路排序:取出排序字段进行排序,排序完成后再次回表查询所需要的其他字段
如果不在索引列上,filesort有两种算法: mysql就要启动双路排序和单路排序
双路排序(慢)
Select id,age,name from stu order by name;
Ø MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和order by列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出
Ø 从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。
Ø 取一批数据,要对磁盘进行两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。
单路排序(快)
从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。
结论及引申出的问题
但是用单路有问题
在sort_buffer中,单路比多路要多占用很多空间,因为单路是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取sort_buffer容量大小,再排……从而多次I/O。
单路本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
优化策略
Ø 增大sort_buffer_size参数的设置
Ø 增大max_length_for_sort_data参数的设置
Ø 减少select 后面的查询的字段。 禁止使用select *
提高Order By的速度
Order by时select * 是一个大忌。只Query需要的字段, 这点非常重要。在这里的影响是:
当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。
两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。
尝试提高 sort_buffer_size
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程(connection)的 1M-8M之间调整。 MySQL5.7和8.0,InnoDB存储引擎默认值是1048576字节,1MB。
SHOW VARIABLES LIKE ‘%sort_buffer_size%’;
尝试提高 max_length_for_sort_data
提高这个参数, 会增加用改进算法的概率。
SHOW VARIABLES LIKE ‘%max_length_for_sort_data%’;
#5.7默认1024字节
#8.0默认4096字节
但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率。如果需要返回的列的总长度大于max_length_for_sort_data,使用双路算法,否则使用单路算法。1024-8192字节之间调整
group by 使用索引的原则几乎跟order by一致 ,唯一区别:
应该创建索引,使用的时候尽量使用is null判断。
EXPLAIN SELECT * FROM emp WHERE emp.name IS NULL;
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL; --索引失效
**注意:**当数据库中的数据的索引列的NULL值达到比较高的比例的时候
,即使在IS NOT NULL 的情况下 MySQL的查询优化器会选择使用索引,此时type的值是range(范围查询)
-- 将 id>20000 的数据的 name 值改为 NULL
UPDATE emp SET `name` = NULL WHERE `id` > 20000;
-- 执行查询分析,可以发现 IS NOT NULL 使用了索引
-- 具体多少条记录的值为NULL可以使索引在IS NOT NULL的情况下生效,由查询优化器的算法决定
EXPLAIN SELECT * FROM emp WHERE emp.name IS NOT NULL
不一定会失效,每一条sql具体有没有使用索引 可以通过trace追踪一下
最好还是给上默认值
数字类型的给0,字符串给个空串“”,
参考上一题
当MySQL接收到客户端的查询SQL之后,仅仅只需要对其进行相应的权限验证之后,就会通过Query Cache来查找结果,甚至都不需要经过Optimizer模块进行执行计划的分析优化,更不需要发生任何存储引擎的交互
mysql5.7支持内部缓存,8.0之后就废弃掉了
缓存的意义在于快速查询提升系统性能,可以灵活控制缓存的一致性
mysql缓存的限制
应用层组织缓存,最简单的是使用redis,ehcached等
Connectors(客户端)
MySQL服务器之外的客户端程序,与具体的语言相关,例如Java中的JDBC,图形用户界面SQLyog等。本质上都是在TCP连接上通过MySQL协议和MySQL服务器进行通信。
MySQL Server(服务器)
第1层:连接层
第一件事就是建立 TCP 连接
。身份认证、权限获取
。
,会收到一个
Access denied for user错误,客户端程序结束执行
用户名密码认证通过
,会从权限表查出账号拥有的权限
与连接关联,之后的权限判断逻辑,都将依赖于此时读到的权限第2层:服务层
Management Serveices & Utilities: 系统管理和控制工具
SQL Interface:SQL接口:
接收用户的SQL命令,并且返回用户需要查询的结果。
比如SELECT … FROM就是调用SQL InterfaceParser:解析器:
语法分析、语法解析
,并为其创建语法树
。语法分析
语法分析主要是把输入转化成若干个tokens,包含key和非key。
在分析之后,会得到4个Token,其中有2个key,它们分别是SELECT、FROM。
key | 非key | key | 非key |
---|---|---|---|
SELECT | age | FROM | user |
Optimizer:查询优化器:
确定SQL语句的执行路径,生成一个执行计划
。Caches & Buffers: 查询缓存组件:
第3层:引擎层
插件式存储引擎层( Storage Engines),负责MySQL中数据的存储和提取,对物理服务器级别维护的底层数据执行操作,服务器通过API与存储引擎进行通信
。不同的存储引擎具有的功能不同,管理的表有不同的存储结构,采用的存取算法也不同,这样我们可以根据自己的实际需要进行选取。例如MyISAM引擎和InnoDB引擎。
存储层
所有的数据、数据库、表的定义、表的每一行的内容、索引,都是存在文件系统
上,以文件的方式存在,并完成与存储引擎的交互。
1.5、查询流程说明
首先,MySQL客户端通过协议与MySQL服务器建连接,通过SQL接口发送SQL语句,先检查查询缓存,如果命中,直接返回结果,否则进行语句解析。
也就是说,在解析查询之前,服务器会先访问查询缓存,如果某个查询结果已经位于缓存中,服务器就不会再对查询进行解析、优化、以及执行。它仅仅将缓存中的结果返回给用户即可,这将大大提高系统的性能。
接下来,MySQL解析器通过关键字将SQL语句进行解析,并生成一棵对应的解析树,
解析器使用MySQL语法规则验证和解析SQL语句。例如,它将验证是否使用了错误的关键字,或者使用关键字的顺序是否正确,引号能否前后匹配等;预处理器则根据MySQL规则进一步检查解析树是否合法,
例如,这里将检查数据表和数据列是否存在,还会解析名字和别名,看是否有歧义等。然后预处理器会进行查询重写,生成一棵新解析树。
接下来,查询优化器将解析树转化成执行计划。
MySQL优化程序会对我们的语句做一些优化,如子查询转换为连接、表达式简化等等。优化的结果就是生成一个执行计划,这个执行计划表明了应该使用哪些索引执行查询,以及表之间的连接顺序是啥样,等等。我们可以使用EXPLAIN语句来查看某个语句的执行计划。
最后,进入执行器阶段。
完成查询优化后,查询执行引擎
会按照生成的执行计划调用存储引擎提供的接口执行SQL查询并将结果返回给客户端。在MySQL8以下的版本,如果设置了查询缓存,这时会将查询结果进行缓存,再返回给客户端。
是在Parser:解析器 分析sql语法的时候检查的列。
在优化器阶段Optimizer:查询优化器:
查看MySQL提供什么存储引擎
SHOW ENGINES;
下面的结果表示MySQL中默认使用的存储引擎是InnoDB,支持事务,行锁,外键,支持分布式事务(XA),支持保存点(回滚)
也可以通过以下语句查看默认的存储引擎:
SHOW VARIABLES LIKE '%default_storage_engine%';
1. InnoDB存储引擎
InnoDB是MySQL的默认事务型引擎,它被设计用来处理大量的短期(short-lived)事务
。可以确保事务的完整提交(Commit)和回滚(Rollback)。
除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎
。
数据文件结构:
表名.frm 存储表结构(MySQL8.0时,合并在表名.ibd中)
表名.ibd 存储数据和索引
InnoDB不仅缓存索引还要缓存真实数据, 对内存要求较 高 ,而且内存大小对性能有决定性的影响。
2. MyISAM存储引擎
MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务和行级锁
,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。
优势是访问的 速度快 ,对事务完整性没有要求或者以SELECT、INSERT为主的应用。
数据文件结构:
表名.frm 存储表结构
表名.MYD 存储数据
表名.MYI 存储索引
MyISAM只缓存索引,不缓存真实数据。
3. Archive引擎
Archive档案存储引擎只支持INSERT和SELECT操作
。4. Blackhole引擎
Blackhole引擎没有实现任何存储机制,它会丢弃所有插入的数据,不做任何保存
。5. CSV引擎
CSV引擎可以将普通的CSV文件作为MySQL的表来处理,但不支持索引
。6. Memory引擎
7. Federated引擎
Federated引擎是访问其他MySQL服务器的一个代理(跨库关联查询)
,尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的。https://dev.mysql.com/doc/refman/5.7/en/innodb-architecture.html
下面是官方的InnoDB引擎结构图,主要分为内存结构和磁盘结构两大部分。
内存区域
Buffer Pool:在InnoDB访问表记录和索引时会在Buffer Pool的页中缓存,以后使用可以减少磁盘IO操作,提升效率。主要用来缓存热的数据页和索引页。
Log Buffer:用来缓存redolog
Adaptive Hash Index:自适应哈希索引
Change Buffer:它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(Buffer Changes),等未来数据被读取时,再将数据合并(Merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能。
磁盘区域
磁盘中的结构分为两大类:表空间和重做日志。
官方文档:
https://dev.mysql.com/doc/refman/8.0/en/innodb-storage-engine.html
方法1:
设置默认存储引擎:
SET DEFAULT_STORAGE_ENGINE=MyISAM;
方法2:
或者修改 my.cnf 文件:vim /etc/my.cnf
新增一行:default-storage-engine=MyISAM
重启MySQL:systemctl restart mysqld
方法3:
我们可以为 不同的表设置不同的存储引擎
CREATE TABLE 表名( 建表语句; ) ENGINE = 存储引擎名称;
ALTER TABLE 表名 ENGINE = 存储引擎名称;
开发存储引擎并不难,难的是开发出来高效的有意义的存储引擎。
简单例子可以看一下官方源码中的示例,可以实现一个什么也没做的存储引擎。
有兴趣可以参考官方文档:https://dev.mysql.com/doc/dev/mysql-server/latest/
外键 事务 锁
对比项 | MyISAM | InnoDB |
---|---|---|
外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 | 行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
关注点 | 并发查询,节省资源、消耗少、简单业务 | 并发写、事务、多表关系、更大资源 |
默认安装 | Y | Y |
默认使用 | N | Y |
自带系统表使用 | Y | N |
除非几乎没有写操作全部都是高频的读操作可以选择MyISAM作为表的存储引擎,其他业务可以一律使用InnoDB。
事务:
是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;
这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;
事务是一组不可再分割的操作集合(工作逻辑单元)
事务都有 ACID 特性
1 、原子性 atomicity
过程的保证
只做一个步骤
1 给钱
2 去买
3 交回来
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
2 、一致性 consistency
结果的保证
保证要吃完 刚张嘴挂了,失去一致性
事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
3 、隔离性 isolation
并发事务互相干扰
不被干扰 刚张嘴别人塞了东西
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
4 、持续性 永久性 durability
保存 吃到肚子里
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
多个事务并发执行一定会产生相互争夺资源的问题
脏读(Dirty read)
是一个事务在处理过程中读取了另外一个事务未提交的数据
当一个事务正在访问数据并且对其进行了修改,但是还没提交事务,这时另外一个事务也访问了这个数据,然后使用了这个数据,因为这个数据的修改还没提交到数据库,所以另外一个事务读取的数据就是“脏数据”,这种行为就是“脏读”,依据“脏数据”所做的操作可能是会出现问题的。
修改丢失(Lost of modify)
*是指一个事务读取一个数据时,另外一个数据也访问了该数据,那么在第一个事务修改了这个数据之后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,这种情况就被称为**修改丢失
不可重复读(Unrepeatableread)
**指在一个事务内多次读取同一数据,在这个事务还没结束时,另外一个事务也访问了这个数据并对这个数据进行了修改,那么就可能造成第一个事务两次读取的数据不一致,这种情况就被称为**不可重复读。
幻读(Phantom read)
是指同一个事务内多次查询返回的结果集总数不一样(比如增加了或者减少了行记录)。
幻读与不可重复读类似,幻读是指一个事务读取了几行数据,这个事务还没结束,接着另外一个事务插入了一些数据,在随后的查询中,第一个事务读取到的数据就会比原本读取到的多,就好像发生了幻觉一样,所以称为*幻读*。
不可重复读 针对的是一份数据的修改
幻读 针对的是行数修改
避免事务并发问题是需要付出性能代价的,此时和分布式系统设计一样(CAP定理及base理论),为了保证一致性就一定会牺牲性能,要做取舍
在mysql内部通过加锁的方式实现好了解决方案可供选择,就是配置事务隔离级别
事务隔离级别 脏读 不可重复读(被修改) 幻读(删减)
读未提交(read-uncommitted) 是 是 是
不可重复读(read-committed) 否 是 是
可重复读(repeatable-read) 否 否 是
串行化(serializable) 否 否 否
MySQL InnoDB
存储引擎默认的事务隔离级别是可重复读(REPEATABLE-READ)
MySQL 5.7 SELECT @@tx_isolation;
MySQL 8.0 SELECT @@transaction_isolation;
隔离级别越低,事务请求的锁越少相应性能也就越高,如没有特殊要求或有错误发生,使用默认的隔离级别即可,如果系统中有高频读写并且对一致性要求高那么就需要比较高的事务隔离级别甚至串行化。
提升事务级别的目的本质是提供更高的数据一致性,如果前置有缓存,那么缓存只能提供高效读并不能保证数据及时一致性,相反的我们还需要对缓存管理有额外的开销。
隔离的实现主要是读写锁和MVCC
锁定读
使用到了读写锁
读写锁是最简单直接的的事务隔离实现方式
锁机制,解决的就是多个事务同时更新数据,此时必须要有一个加锁的机制
下列操作属于锁定读
select ... lock in share mode
select ... for update
insert、update、delete
非锁定读
v10 -> age=18
v11 ->age=19
v12 ->age=15
使用mvcc 多版本控制实现
https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html
Multi-Version Concurrency Control 多版本并发控制,MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问
InnoDB是一个多版本的存储引擎。它保存有关已更改行的旧版本的信息,以支持并发和回滚等事务特性。这些信息存储在一个称为回滚段的数据结构中的系统表空间或undo表空间中。参见第14.6.3.4节“撤消表空间”。InnoDB使用回滚段中的信息来执行事务回滚所需的撤消操作。它还使用这些信息构建行的早期版本,以实现一致的读取
MVCC 的实现依赖于:隐藏字段、Read View、undo log
隐藏字段
DB_TRX_ID
用来标识最近一次对本行记录做修改 (insert 、update) 的事务的标识符 ,即最后一次修改本行记录的事务 id。 如果是 delete 操作, 在 InnoDB 存储引擎内部也属于一次 update 操作,即更新行中的一个特殊位 ,将行标识为己删除,并非真正删除。DB_ROLL_PTR
回滚指针,指向该行的 undo log 。如果该行未被更新,则为空.DB_ROW_ID
如果没有设置主键且该表没有唯一非空索引时,InnoDB
会使用该 id 来生成聚簇索引.Read View
不同的事务隔离级别中,当有事物在执行过程中修改了数据(更新版本号),在并发事务时需要判断一下版本链中的哪个版本是当前事务可见的。为此InnoDB有了ReadView的概念,使用ReadView来记录和隔离不同事务并发时此记录的哪些版本是对当前访问事物可见的。
undo log
除了用来回滚数据,还可以读取可见版本的数据。以此实现非锁定读
首先是通过锁和mvcc实现了执行过程中的一致性和原子性
其次是在灾备方面通过Redo log实现,Redo log会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统崩溃重启后可以把事务所做的任何修改都恢复出来。
使用Redo log保证了事务的持久性。当事务提交时,必须先将事务的所有日志写入日志文件进行持久化,就是我们常说的WAL(write ahead log)机制,如果出现断电重启便可以从redolog中恢复,如果redolog写入失败那么也就意味着修改失败整个事务也就直接回滚了。
表级锁:串行化(serializable)时,整表加锁,事务访问表数据时需要申请锁,虽然可分为读锁和写锁,但毕竟是锁住整张表,会导致并发能力下降,一般是做ddl处理时使用
行级锁:除了串行化(serializable)时 InnoDB使用的都是行级锁,只锁一行数据,其他行数据不影响,并发能力强。
行级锁实现比较复杂不是单纯锁住一行数据,是由mvcc完成的。
共享锁或S锁,其它事务可以继续加共享锁,但不能加排它锁
排它锁或X锁,在进行写操作之前要申请并获得,其它事务不能再获得任何锁。
它分为意向共享锁(IS)和意向排他锁(IX)
一个事务对一张表的某行添加共享锁前,必须获得对该表一个IS锁或者优先级更高的锁。
一个事务对一张表的某行添加排他锁之前,它必须对该表获取一个IX锁。
意向锁属于表锁,它不与innodb中的行锁冲突,任意两个意向锁之间也不会产生冲突,但是会与表锁(S锁和X锁)产生冲突
表锁,行锁,间隙锁,Next-Key锁等
在Serializable中读加共享锁,写加排他锁,读写互斥
两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)
当前读 :在锁定读(使用锁隔离事物)的时候读到的是最新版本的数据
快照读:可重复读(repeatable-read)下 mvcc生效读取的是数据的快照,并不是最新版本的数据(未提交事物的数据)
https://dev.mysql.com/doc/refman/8.0/en/xa.html
AP(Application Program):应用程序,定义事务边界(定义事务开始和结束)并访问事务边界内的资源。
RM(Resource Manger)资源管理器: 管理共享资源并提供外部访问接口。供外部程序来访问数据库等共享资源。此外,RM还具有事务的回滚能力。
TM(Transaction Manager)事务管理器:TM是分布式事务的协调者,TM与每个RM进行通信,负责管理全局事务,分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚、失败恢复等。
应用程序AP向事务管理器TM发起事务请求
TM调用xa_open()建立同资源管理器的会话
TM调用xa_start()标记一个事务分支的开头
AP访问资源管理器RM并定义操作,比如插入记录操作
TM调用xa_end()标记事务分支的结束
TM调用xa_prepare()通知RM做好事务分支的提交准备工作。其实就是二阶段提交的提交请求阶段。
TM调用xa_commit()通知RM提交事务分支,也就是二阶段提交的提交执行阶段。
TM调用xa_close管理与RM的会话。
xa_start:负责开启或者恢复一个事务分支,并且管理XID到调用线程
xa_end:负责取消当前线程与事务分支的关系
xa_prepare:负责询问RM 是否准备好了提交事务分支 xa_commit:通知RM提交事务分支
xa_rollback:通知RM回滚事务分支
mysql的xa事务分为两部分:
5.7 SHOW VARIABLES LIKE '%innodb_support_xa%';
8.0 默认开启无法关闭
XA 事务语法示例如下:
XA START '自定义事务id';
SQL语句...
XA END '自定义事务id';
XA PREPARE '自定义事务id';
XA COMMIT\ROLLBACK '自定义事务id';
XA PREPARE 执行成功后,事务信息将被持久化。即使会话终止甚至应用服务宕机,只要我们将【自定义事务id】记录下来,后续仍然可以使用它对事务进行 rollback 或者 commit。
xa事务可以跨库或跨服务器,属于分布式事务,同时xa事务还支撑了InnoDB内部日志两阶段记录
普通事务只能在单库中执行
两阶段提交协议与3阶段提交协议,额外增加了参与的角色保证分布式事务完成更完善
查询库存 = 100 0 扣减库存 = -1 99
记录日志 = log
提交 commit
select本身是一个查询语句,查询语句是不会产生冲突的一种行为,一般情况下是没有锁的,用select for update 会让select语句产生一个排它锁(X), 这个锁和update的效果一样,会使两个事务无法同时更新一条记录。
https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html
https://dev.mysql.com/doc/refman/8.0/en/select.html
for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。
在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。
InnoDB默认是行级别的锁,在筛选条件中当有明确指定主键或唯一索引列的时候,是行级锁。否则是表级别。
示例
SELECT … FOR UPDATE [OF column_list][WAIT n|NOWAIT][SKIP LOCKED];
select * from t for update 会等待行锁释放之后,返回查询结果。
select * from t for update nowait 不等待行锁释放,提示锁冲突,不返回结果
select * from t for update wait 5 等待5秒,若行锁仍未释放,则提示锁冲突,不返回结果
select * from t for update skip locked 查询返回查询结果,但忽略有行锁的记录
事务 a
表 t id=100 更新 加行锁
表 t id=200 更新 已加锁
事务 b
表 t id=200 更新 加行锁
表 t id=100 更新 已加锁
排查:
show status like "innodb_row_lock%";
lnnodb_row_lock_current_waits:当前正在等待锁定的数量;
lnnodb_row_lock_time :从系统启动到现在锁定的总时间长度,单位ms;
Innodb_row_lock_time_avg :每次等待所花平均时间;
Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间;
lnnodb_row_lock_waits :从系统启动到现在总共等待的次数。
解决:
死锁无法避免,上线前要进行严格的压力测试
快速失败
拆分sql,严禁大事务
充分利用索引,优化索引,尽量把有风险的事务sql使用上覆盖索,优化where条件前缀匹配,提升查询速度,引减少表锁
无法避免时:
error log主要记录MySQL在启动、关闭或者运行过程中的错误信息,在MySQL的配置文件my.cnf中,可以通过log-error=/var/log/mysqld.log 执行mysql错误日志的位置。
0.1秒
Ø MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。
Ø long_query_time的默认值为10,意思是运行10秒以上的语句。
Ø 由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前explain进行全面分析。
Ø 默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。
Ø 当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow。
general log 记录了客户端连接信息以及执行的SQL语句信息,通过MySQL的命令
重写日志(redo log)
回滚日志(undo log)
二进制日志(bin log)
MySQL的bin log日志是用来记录MySQL中增删改时的记录日志。
当你的一条sql操作对数据库中的内容进行了更新,就会增加一条bin log日志。查询操作不会记录到bin log中。
bin log最大的用处就是进行主从复制,以及数据库的恢复。
redo log是一种基于磁盘的数据结构,用来在MySQL宕机情况下将不完整的事务执行数据纠正,redo日志记录事务执行后的状态。
当事务开始后,redo log就开始产生,并且随着事务的执行不断写入redo log file中。redo log file中记录了xxx页做了xx修改的信息,我们都知道数据库的更新操作会在内存中先执行,最后刷入磁盘。
redo log就是为了恢复更新了内存但是由于宕机等原因没有刷入磁盘中的那部分数据。
undo log主要用来回滚到某一个版本,是一种逻辑日志。
undo log记录的是修改之前的数据,比如:当delete一条记录时,undolog中会记录一条对应的insert记录,从而保证能恢复到数据修改之前。在执行事务回滚的时候,就可以通过undo log中的记录内容并以此进行回滚。
undo log还可以提供多版本并发控制下的读取(MVCC)。
磁盘写入固然是比较慢的。
参数:sync_binlog
binlog 写入策略:
1、sync_binlog=0 的时候,表示每次提交事务binlog不会马上写入到磁盘,而是先写到page cache,相对于磁盘写入来说写page cache要快得多,不过在Mysql 崩溃的时候会有丢失日志的风险。
2、sync_binlog=1 的时候,表示每次提交事务都会执行 fsync 写入到磁盘 ;
3、sync_binlog的值大于1 的时候,表示每次提交事务都 先写到page cach,只有等到积累了N个事务之后才fsync 写入到磁盘,同样在此设置下Mysql 崩溃的时候会有丢失N个事务日志的风险。
很显然三种模式下,sync_binlog=1 是强一致的选择,选择0或者N的情况下在极端情况下就会有丢失日志的风险,具体选择什么模式还是得看系统对于一致性的要求。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hn295WDy-1670057005908)(16701032-f8547d110ba34135.png)]
innodb_flush_log_at_trx_commit
取值0:每秒(一秒钟内提交的事务)写入磁盘 每秒触发一次缓存日志回写磁盘操作,并调用操作系统fsync刷新IO缓存。
取值1:有事务提交就立即刷盘 每次提交事务都立即调用操作系统fsync刷新IO缓存。
取值2:每次事务提交 都写给操作系统 由系统接管什么时候写入磁盘 每次都把redo log写到系统的page cache中,由系统接管什么时候写入磁盘
时机顺序:
1 开启事务
2 查询数据库中需要更新的字段,加载到内存中 形成数据脏页
3 记录undo log到内存缓冲区(用于回滚和mvcc)并关联redo log -> 可刷盘
4 记录 redo log到内存缓冲区 (用于失败重放)准备提交事务 -> 可刷盘
5 修改内存中的脏页数据
6 提交事务触发redolog刷盘
7 undo log 和脏页 刷盘
8 事务成功
redo log 与 binlog 的两阶段提交
redo log 的写入拆成了两个步骤:prepare 和 commit
prepare:redolog写入log buffer,并fsync持久化到磁盘,在redolog事务中记录2PC的XID,在redolog事务打上prepare标识
commit:binlog写入log buffer,并fsync持久化到磁盘,在binlog事务中记录2PC的XID,同时在redolog事务打上commit标识
logbin格式:
可以使用 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';
l 全局变量设置,对所有客户端有效。但,必须是设置后进行登录的客户端。
SET GLOBAL long_query_time=0.1;
l 对当前会话连接立即生效,对其他客户端无效。
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;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-elyDsc4A-1670057005909)(image-20220710101402666.png)]
注意:
内连接时,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;
这里的<derived2>
就是在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过高来追踪定位
垂直分库
一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同 的数据库上面,这样也就将数据或者说压力分担到不同的库上面,如下图:
系统被切分成了,用户,订单交易,支付几个模块。
水平分表
把一张表里的内容按照不同的规则 写到不同的库里
相对于垂直拆分,水平拆分不是将表做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些行切分 到一个数据库,而另外的某些行又切分到其他的数据库中,如图:
大数据量下可以配合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中的映射失效
索引失效
建议:建个新表,导入数据后重命名
指的是在一台主机上部署多个实例
主要目的是压榨服务器性能
缺点是互相影响
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。