索引的本质:是存储引擎用于快速找到数据记录的一种数据结构,MySQL中进行数据查找时,首先查看查询条件是否命中某条索引,符合则通过索引查找相关数据,如果不符合则需要全表扫描,即需要一条一条地查找记录,直到找到与条件符合的记录,建索引,目的就是为了减少磁盘I/O的次数,加快查询速率
索引是在存储引擎中实现的,因此每种存储引擎的索引不一定完全相同,并且每种存储引擎不一定支持所有索引类型。同时,存储引擎可以定义每个表的最大索引数和最大索引长度。所有存储引擎支持每个表至少16个索引,总索引长度至少为256字节
类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本,这也是创建索引最主要的原因
通过创建唯一索引,可以保证数据库表中每一行数据的唯一性
在实现数据的参考完整性方面,可以加速表和表之间的连接,对于有依赖关系的子表和父表联合查询时,可以提高查询速度
在使用分组和排序子句进行数据查询时,可以显著减少查询中分组和排序的时间,降低了CPU的消耗
创建索引和维护索引要耗费时间,并且随着数据量的增加,所耗费的时间也会增加
索引需要占磁盘空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,存储在磁盘上,如果有大量的索引,索引文件就可能比数据文件更快达到最大文件尺寸
虽然索引大大提高了查询速度,同时却会降低更新表的速度。当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度
注意:
- 索引可以提高查询的速度,但是会影响插入记录的速度。这种情况下,最好的办法是先删除表中的索引,然后插入数据,插入完成后再创建索引
SELECT [列名列表] FROM 表名 WHERE 列名= xxx;
假设目前表中的记录比较少,所有的记录都可以被存放到一个页中,在查找记录的时候可以根据搜索条件的不同分为两种情况:
以主键为搜索条件
可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定记录
以其他列作为搜索条件
在数据页中并没有对非主键列建立所谓的页目录,所以无法通过二分法快速定位相应的槽。这种情况下只能从最小记录开始依次遍历单链表中的每条记录,然后对比每条记录是不是符合搜索条件
在很多页中查找记录可以分为两个步骤:
在没有索引的情况下,不论是根据主键列或者其他列的值进行查找,由于不能快速的定位到记录所在的页,所以只能从第一个页沿着双向链表一直往下找,在每一个页中根据在一个页中的查找方式去查找指定的记录。因为要遍历所有的数据页,所以这种方式非常耗时的
建一个表:
CREATE TABLE index_demo(
c1 INT,
c2 INT,
c3 CHAR(1),
PRIMARY KEY(c1)
) ROW_FORMAT = Compact;
在index_demo表中有2个INT类型的列,1个CHAR(1)类型的列,而且规定了c1列为主键,这个表使用Compact行格式来实际存储记录的。index_demo表的行格式示意图:
把一些记录放到页里的示意图就是:
下一个数据页中用户记录的主键值必须大于上一个数据页中用户记录的主键值
假设:每个数据结构最多能存放3条记录
INSERT INTO index_demo VALUES(1,4,'u'),(3,9,'d'),(5,3,'y');
这些记录按照主键值的大小串联成一个单向链表
假设3条记录插入了index_demo 表中编号为10的数据页中。此时再插入一条记录
INSERT INTO index_demo VALUES(4,4,'a');
因为数据结构最多只能放3条记录,所以不得不再分配一个新页
注意:新分配的数据页编号可能并不是连续的
页10中用户记录最大的主键值是5,而页28中有一条记录的主键值是4,因为5>4,所以不符合下一个数据页中用户记录的主键值必须大于上一个数据页中用户记录的主键值的要求,所以在插入主键值为4的记录的时候需要把主键值为5的记录移动到页28中,然后再把主键值为4的记录插入到页10中
这个过程表明了在对页中的记录进行增删改查操作时,必须通过一些诸如记录移动的操作来始终保证这个状态(下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值)一直成立。这个过程称为页分裂
给所有的页建立一个目录项(索引)
由于数据页的编号可能是不连续的,所以在向 index_demo 表中插入许多条记录后,可能是这样的效果:
此时需要给它们做个目录,每个页对应一个目录项,每个目录项包括下边两个部分:
以页28 为例,它对应目录项2 ,这个目录项中包含着该页的页号28 以及该页中用户记录的最小主键值5 。此时只需要把几个目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键值快速查找某条记录的功能
比如:查找主键值为20 的记录,具体查找过程分两步:
- 先从目录项中根据二分法快速确定出主键值为20 的记录在目录项3 中(因为12 < 20 < 209 ),它对应的页是页9
- 再根据在一个页中查找记录的方式去页9中定位具体的记录
总结
因为每个数据结构允许存放的数据记录有限,当存储大量数据记录时,会导致产生很多数据页,然而在查询数据记录时,需要依次从第一页开始查找数据记录,这样会导致查找效率低下,此时索引的作用就是使用每个数据页中最小主键值为key,每个数据页的页号为page_no,能够快速的定位到所需记录所在的数据页,然后再使用一个数据页查找记录的方式查找记录,这样会使查找效率极大地提高
迭代1次:只有一个存放目录项纪录的页
InnoDB使用记录头信息里的record_type属性区分一条记录是普通的用户记录还是目录项记录,它的各自取值代表的意思如下:
从图中可以看出来,新分配了一个编号为30的页来专门存储目录项记录
目录项记录和普通的用户记录的不同点:
目录项记录和普通的用户记录的相同点:
总结:在存放目录项记录的页中使用二分法匹配所要查找的记录的主键,从而得到所要查找记录所在的存放普通的用户记录的页号,然后再在存放普通的用户记录的页中二分法查找对应的主键值,最终得到想要的结果
以查找主键为20 的记录为例,大致分为两步:
- 先到存储目录项记录的页,也就是页30中通过二分法快速定位到对应目录项,因为12 < 20 < 209 ,所以定位到对应的记录所在的页就是页9
- 再到存储用户记录的页9中根据二分法快速定位到主键值为20 的用户记录
迭代2次:有多个存放目录项纪录的页
从图中可以看出,插入了一条主键值为320的用户记录之后需要两个新的数据页:
总结:存在多个存放目录项记录的页时,相互也保持着一种状态(当前页的最小主键值一定大于上一页的最大主键值),所以只能按顺序,从第一个存放目录项记录的页开始依次查找,得到所需主键所在的存放目录项记录的页,然后在存放目录项记录的页中二分法查找主键对应的存放普通用户记录的页号,得到存放普通用户记录的页号,在对应页中二分法查找对应的主键值,最终得到所需的记录
以查找主键值为20 的记录为例,大致分为三步:
- 确定目录项记录页,现在的存储目录项记录的页有两个,即页30 和页32 ,又因为页30表示的目录项的主键值的范围是[1,320),页32表示的目录项的主键值不小于320,所以主键值为20 的记录对应的目录项记录在页30中。
- 通过目录项记录页确定用户记录真实所在的页
- 在真实存储用户记录的页中定位到具体的记录
迭代3次:存放目录项记录的页的目录页
如果表中的数据非常多则会产生很多存储目录项记录的页,为这些存储目录项记录的页再生成一个更高级的目录
如图,生成了一个存储更高级目录项的页33 ,这个页中的两条记录分别代表页30和页32,如果用户记录的主键值在[1,320)之间,则到页30中查找更详细的目录项记录,如果主键值不小于320 的话,就到页32中查找更详细的目录项记录
我们可以用下边这个图来描述它:
这个数据结构,它的名称是 B+树
B+Tree
一个B+树的节点其实可以分成好多层,规定最下边的那层,也就是存放用户记录的那层为第0层,之后依次往上加,假设所有存放用户记录的叶子节点代表的数据页可以存放100条用户记录,所有存放目录项记录的内节点代表的数据页可以存放1000条目录项记录,那么:
一般情况下,用到的B+树都不会超过4层,通过主键值去查找某条记录最多只需要做4个页面内的查找(查找3个目录项页和一个用户记录页),又因为在每个页面内有Page Directory(页目录),所以在页面内也可以通过二分法实现快速定位记录
索引按照物理实现方式,索引可以分为2种:聚簇(聚集)和非聚簇(非聚集)索引(也称为二级索引或者辅助索引)
聚簇索引将索引和数据保存在同一个B+树中
聚簇:表示当前数据行和相邻的键值聚簇的存储在一起
特点:
使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:
B+树的叶子节点存储的是完整的用户记录(存储了所有列的值(包括隐藏列))
把具有这两种特性的B+树称为聚簇索引,所有完整的用户记录都存放在这个聚簇索引的叶子节点处。InnDB存储引擎会自动的创建聚簇索引
优点:
缺点:
以别的列作为搜索条件时,可以多建几颗B+树,不同的B+树中的数据采用不同的排列规则,当以别的列作为搜索条件时,建立的B+树中的叶子节点存放的是别的列的值以及所对应的页号,并不会直接存放完整的用户记录
概念:回表
概念:叶子节点存放
如果把完整的用户记录放到叶子结点是可以不用回表。但是太占地方了,相当于每建立一课B+树都需要把所有的用户记录再都拷贝一遍,太浪费存储空间
因为这种按照非主键列建立的B+树需要一次回表操作才可以定位到完整的用户记录,所以这种B+树也被称为二级索引,或者辅助索引
非聚簇索引的存在不影响数据在聚簇索引中的组织,所以一张表可以有多个非聚簇索引
小结
聚簇索引与非聚簇索引的原理不同,在使用上也有一些区别:
可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说想让B+树按照 c2和c3列的大小进行排序,这个包含两层含义:
为c2和c3建立的索引的示意图如下:
需要注意:
注意一点,以c2和c3列的大小为排序规则建立的B+树称为联合索引,本质上也是一个二级索引。它的意思与分别为c2和c3列分别建立索引的表述是不同的,不同点如下:
实际上B+树的形成过程是这样的:
这个过程特别注意的是:一个B+树索引的根节点自诞生之日起,便不会再移动。这样只要对某个表建立一个索引,那么它的根节点的页号便会被记录到某个地方。当InnoDB存储引擎需要用到这个索引的时候,都会从哪个固定的地方取出根节点的页号,从而来访问这个索引
B+树索引的内节点中目录项记录的内容是索引列+页号的搭配,假设这个表中的数据是这样的:
如果二级索引中目录项记录的内容只是索引列+页号的搭配的话,那么为c2列建立索引后的B+树应该长这样:
如果想新插入一行记录,其中c1、c2、c3的值分别是: 9、1、c,那么在修改这个为 c2 列建立的二级索引对应的 B+树时:由于页3中存储的目录项记录是由c2列+页号的值构成的,页3中的两条目录项记录对应的 c2 列的值都是1,而新插入的这条记录的 c2 列的值也是1,为了让新插入记录找到自己在那个页面,需要保证在B+树的同一层页节点的目录项记录除页号这个字段以外是唯一的。所以对于二级索引的内节点的目录项记录的内容实际上是由三个部分构成的:
即把主键值也添加到二级索引内节点中的目录项记录,这样就能保住 B+树每一层节点中各条目录项记录除页号这个字段外是唯一的,所以为c2建立二级索引后的示意图实际上应该是这样子的:
当插入记录(9,1,‘c’)时,由于页3中存储的目录项记录是由c2列+主键+页号的值构成的,可以先把新纪录的c2列的值和页3中各目录项记录的c2列的值作比较,如果c2列的值相同的话,可以接着比较主键值,因为B+树同一层中不同目录项记录的c2列+主键的值肯定是不一样的,所以最后肯定能定位唯一的一条目录项记录,在本例中最后确定新纪录应该被插入到页5中
InnoDB 的一个数据页至少可以存放两条记录
B树索引使用存储引擎如表所示:
索引/存储引擎 | MyISAM | InnoDB | Memory |
---|---|---|---|
B-Tree索引 | 支持 | 支持 | 支持 |
即使多个存储引擎支持同一种类型的索引,但是他们的实现原理也是不同的。Innodb和MyISAM默认的索引是Btree索引;而Memory默认的索引是Hash索引
MyISAM引擎使用 B+Tree 作为索引结构,叶子节点的data域存放的是数据记录的地址
InnoDB中索引为数据,即在聚簇索引中b+树的叶子结点中包含了所有完整的用户记录,但MyISAM的索引方案是将索引和数据分开存储:
假设表中一共有三列,Col1为主键,图为一个MyISAM表的主索引(primary key),可以看出MyISAM的索引文件仅保存数据记录的地址
MyISAM中,主键索引和二级索引在结构上没有区别,唯一的区别为主键索引要求的key唯一,二级索引要求的key可以重复,图为在Col2建立的一个二级索引
MyISAM中索引的检索算法:首先按照B+树搜索算法搜索索引,如果指定的key存在,则取出其data域的值,然后以data域的值为地址,读取响应地址存放的数据记录
MyISAM的索引方式都是“非聚簇”的,与InnoDB包含1个聚簇索引是不同的
两种引擎中索引的区别:
在InnoDB存储引擎中,只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录,在MyISAM中需要进行一次回表操作,意味着MyISAM中建立的索引相当于全部都是二级索引
InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址
InnoDB的非聚簇索引data域存储相应记录主键的值,而MyISAM索引记录的是地址
MyISAM是拿着地址偏移量直接到文件中取数据,所以回表操作是十分快速的,InnoDB是通过获取主键之后再去聚簇索引里找记录,速度没有MyISAM快
InnoDB要求表必须有主键( MyISAM可以没有)。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键
小结:
索引在空间和时间上都会有消耗:
空间上的代价
每建立一个索引都要为它建立一棵B+树,每一棵B+树的每一个节点都是一个数据页,一个页默认会占用16KB 的存储空间,一棵很大的B+树由许多数据页组成,那就是很大的一片存储空间。
时间上的代价
每次对表中的数据进行增、删、改操作时,都需要去修改各个B+树索引。而且B+树每层节点都是按照索引列的值从小到大的顺序排序而组成了双向链表。不论是叶子节点中的记录,还是内节点中的记录都是按照索引列的值从小到大的顺序而形成了一个单向链表。而增、删、改操作可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行一些记录移位,页面分裂、页面回收等操作来维护好节点和记录的排序
一个表上索引建的越多,就会占用越多的存储空间,在增删改记录的时候性能就越差。为了能建立又好又少的索引,我们得学学这些索引在哪些条件下起作用的。
磁盘I/O操作次数对索引的使用效率至关重要
按数组的方式,从数组第一个位置开始依次查找,这样查找,导致磁盘I/O操作次数很多,即索引效率很低
Hash函数可以帮助大幅度提升索引的效率,Hash算法是通过某种确定的算法将输入转变为输出
例如:
如果想要验证两个文件是否相同,只需要把一个文件通过Hash函数计算得到的值与另一个文件通过Hash函数计算得到的值做对比,如果值相同,则是同一个文件,否则不是同一个文件
效率方面来说,Hash检索基本只需要一次就可以,B+树检索需要从根结点依次向下查找,多次访问节点才可以找到数据,所以Hash比B+树更快
Hash函数可以根据关键字K,通过hash函数h(k)求出关键字k所在哈希表中槽的位置
当两个不同的关键字映射到相同的位置时,在数据库中一般使用链接法解决,将散列到同一槽位的元素放到一个链表中
数组方式的全表查询和hash方式的全表查询的效率比较
@Test
//数组方式检索效率
public void testSQLplus1() {
int[] arr = new int[100000];
for(int i = 0;i < arr.length;i++){
arr[i]= i +1;
}
long start = System.currentTimeMillis();
for(int j = 1; j<=100000;j++){
int temp = j;
for(int i = 0;i < arr.length;i++){
if(temp == arr[i]){
break;
}
}
}
long end = System.currentTimeMillis();
System.out.println("time:"+(end - start)); //time:2359
}
@Test
//hash方式检索效率
public void testSQLplus2(){
HashSet<Integer> set = new HashSet<>(100000);
for(int i = 0;i < 100000;i++){
set.add(i +1);
}
long start = System.currentTimeMillis();
for(int j = 1; j<=100000;j++){
int temp = j;
boolean contains = set.contains(temp);
}
long end = System.currentTimeMillis();
System.out.println("time:"+(end - start)); //time:8
}
Hash索引适用存储引擎如表所示:
索引/存储引擎 | MyISAM | InnoDB | Memory |
---|---|---|---|
HASH索引 | 不支持 | 不支持 | 支持 |
Hash索引的适用性:
采用自适应 Hash 索引目的是方便根据 SQL 的查询条件加速定位到叶子节点,特别是当 B+树比较深的时候,通过自适应 Hash 索引可以明显提高数据的检索效率。
通过 innodb_adaptive_hash_index 变量来查看是否开启了自适应 Hash
show variables like '%adaptive_hash_index';
1.二叉搜索树的特点
2.查找规则
为了提高查询效率,就需要减少磁盘IO数。为了减少磁盘IO的次数,就需要尽量降低树的高度,需要树的每层的分叉越多越好
在二叉搜索树的基础上增加了约束,左右两个子树的高度差的绝对值不能超过1,并且左右两个子树都是AVL(平衡二叉树)
平衡二叉树包括:平衡二叉搜索树、红黑树、数堆、伸展树,搜索的时间复杂度O(log2n)
因为每访问一次节点就需要进行一次磁盘 I/O 操作,虽然平衡二叉树的效率高,但是树的深度也同样高,这就意味着磁盘 I/O 操作次数多,会影响整体数据查询的效率,为了解决这个问题,可以将二叉树变成M叉树,这样当数据量 N 大的时候,以及树的分叉树 M 大的时候,M叉树的高度会远小于二叉树的高度(M > 2)
B 树的英文是 Balance Tree,也就是多路平衡查找树。简写为 B-Tree。它的高度远小于平衡二叉树的高度
B 树称为多路平衡查找树,每个节点最多包含M个子节点,M称为B树的阶,每个节点包括了关键字和子节点的指针,子节点指针数量=(关键字数量+1)
一个 M 阶的 B 树(M>2)有以下的特性:
用 B 树进行查找,步骤:每次与根结点关键字,如果比根结点的关键字小,则向左遍历,如果比根结点的关键字大,则向右遍历,如果和根结点关键字相同,则找到需要的关键字返回此根结点即可
注意:
- B树在插入和删除节点时,会导致树不平衡,B树会自动调整节点位置来保持树的自平衡
- 关键字存放在叶子节点和非叶子结点中
- 搜索性能等价于在关键字全集内做二分查找
- 叶子节点和非叶子结点存放的是关键字、子节点指针、表记录中除了主键以外的数据
B+树也是一种多路搜索树,基于B树做出了改进,主流的DBMS都支持B+树的索引方式,相比于B-树,B+树适合文件索引系统,MySQL中采用的是B+树
B+树和 B 树的差异:
B+树与Hash的差异:
B+树的优势
R-Tree在MySQL很少使用,仅支持 geometry数据类型
数据结构 | 查找 | 插入 | 删除 | 遍历 |
---|---|---|---|---|
数组 | O(N) | O(1) | O(N) | – |
有序数组 | O(logN) | O(N) | O(N) | O(N) |
链表 | O(N) | O(1) | O(N) | – |
有序链表 | O(N) | O(N) | O(N) | O(N) |
二叉树(一般情况) | O(logN) | O(logN) | O(logN) | O(N) |
二叉树(最坏情况) | O(N) | O(N) | O(N) | O(N) |
平衡二叉树(一般情况和最坏情况) | O(logN) | O(logN) | O(logN) | O(N) |
哈希表 | O(1) | O() | O(1) | – |
索引信息以及数据记录都是保存在文件(页结构)中,不同的的存储引擎存放的格式不同,InnoDB是MySQL默认的存储引擎
InnoDB将数据划分为若干页,每一页默认大小为16KB
磁盘和内存之间交互的基本单位为页,每次至少在磁盘中读取16KB的内容到内存中,每次至少把内存中16KB的内容刷新到磁盘中,即页是磁盘I/O操作的最小单位,记录的存储是以行来存储的,每一页可以存储多个行记录
各个页在物理结构上不相连,是通过双向链表进行相连的,每一页中的记录按照主键值从小到大说的顺序组成一个单链表,每一页都会为存储的记录生成一个页目录,在通过主键查找记录时可以在页目录中使用二分法快速定位到响应的槽
show variables like '%innodb_page_size%'
MySQL中默认大小为16KB
表空间
段
区
页按类型划分,常见的有数据页(保存B+树节点)、系统表、Undo 页和事物数据页等
数据页的16KB
大小的存储空间被划分为七个部分,分别是文件头(File Header)、页头(Page Header)、最大最小记录(Infimum + supremum)、用户记录(User Records)、空闲空间(Free Space)、页目录(Page Directory)和文件尾(File Tailer)
描述各种页的通用信息。(比如页的编号、其上一页、下一页是谁等)
存储的记录会按照指定的行格式存储到User Records部分,在一开始生成页的时候,并没有User Records这个部分,每当插入一条记录,都会从Free Space部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页
User Records中的这些记录按照指定的行格式一条一条摆在User Records部分,相互之间形成单链表
对于一条完整的记录来说,比较记录的大小就是比较主键的大小
在页中,记录是以单向链表的形式进行存储的。单向链表的特点就是插入、删除非常方便,但是检索效率不高,最差的情况下需要遍历链表上的所有节点才能完成检索。因此在页结构中专门设计了页目录这个模块,专门给记录做一个目录,通过二分查找法的方式进行检索,提升效率
页目录,二分法查找
为了能得到一个数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等,特意在页中定义了一个叫Page Header的部分,这个部分占用固定的56个字节,专门存储各种状态信息
一颗B+树按照字节类型可以分为两部分:
B+树中每一层中的页都会形成一个双向链表,每个页之间的物理位置不确定,所以有可能进行索引是I/O操作很慢,引入区,即在物理位置上连续的64个页,当表中数据量大时,为某个索引分配空间的时候不是按照页为单位分配,而是按照区位单位分配,当不足64个页时也分配一个区,虽然会造成空间的浪费,但会大幅度减少I/O操作
在B+树中为了区分叶子节点和非叶子结点,InnoDB一般会将叶子节点和非叶子结点分别设立独立的区,存放叶子节点的区的集合算是一个段,存放非叶子结点的区的集合算是一个段,即一个索引会生成两个段,叶子节点段和非叶子结点段
常见的段:数据段(B+树的叶子节点)、索引段(B+树的非叶子节点)、回滚段
碎片区直属于表空间,不属于任何一个段,一般刚开始向表中插入数据时,段是从某个碎片区以单个页面为单位分配存储空间的,当某个段已经占用了32个碎片区页面之后,就会申请完整的区为单位来分配存储空间
处于FREE、FREE_FRAG 以及 FULL_FRAG 这三种状态的区都是独立的,直属于表空间。处于 FSEG 状态的区附属于某个段
是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但一个段只能属于一个表空间,数据库由多个表空间构成,表空间分为系统表空间、用户表空间、撤销表空间、临时表空间等
独立表空间,即每张表有一个独立的表空间,数据和索引信息都会保存在自己的表空间中。独立的表空间(即:单表)可以在不同的数据库之间进行迁移
独立表空间结构
真实表空间对应的文件大小
整个MySQL进程只有一个系统表空间,在系统表空间中会额外记录一些有关整个系统信息的页面,这部分在独立表空间中没有
InnoDB从磁盘中读取数据最小单位是数据页
MySQL存放的数据,逻辑概念上称为表,在磁盘等物理层面而言是按数据页形式进行存放的,当其加载到MySQL中称之缓存页
如果缓冲池没有该页数据,缓冲池读取数据的方式,每种方式的读取速率是不同的:
1.内存读取
如果数据存在于内存中,则缓冲池直接在内存中读取数据
2.随机读取
如果数据没有存在于内存中,则需要在磁盘上对该页进行查找
3.顺序读取
针对于批量读取,如果数据没有存在于内存中,则需要在磁盘上批量顺序读取数据
例如,在表student的字段name上建立一个普通索引,查询记录时就可以根据该索引进行查询
例如,在表student的字段email中创建唯一索引,那么email的值就必须唯一,通过唯一索引可以更加快速的确定某条记录
例如,在表student的字段id中创建主键索引,那么id的值就必须唯一且不为空,通过主键索引可以更加快速的确定某条记录
例如,在表student的字段id、name和gender上建立一个idx_id_name_gender索引,只有在查询条件中使用了字段id时该索引才会被使用,使用多列索引是遵循最左前缀集合
例如,在表student的字段information是TEXT类型,该字段包含了很多文字信息,在字段information上建立全文索引后,可以提高查询速度
在CREATE TABLE创建表时,可以定义列的数据类型、主键约束(PRIMARY KEY
)、外键约束(FOREIGN KEY
)或者唯一性约束(UNIQUE
)
举例:
CREATE TABLE dept(
dept_id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(20)
);
CREATE TABLE emp(
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20) UNIQUE,
dept_id INT,
CONSTRAINT emp_dept_id_fk FOREIGN KEY(dept_id) REFERENCES dept(dept_id)
);
显式创建表时创建索引的基本语法格式:
CREATE TABLE tablename(
col_name data_type;...
[UNIQUE|FULLTEXT|SPATIAL][INDEX|KEY] [index_name] (col_name[LENGTH],...)[ASC|DESC]
);
查看索引:
方式一
SHOW INDEX FROM tablename ;
方式二
SHOW CREATE TABLE tablename;
1.创建普通索引
在book表中的year_publication字段上建立普通索引,SQL语句如下:
CREATE TABLE book(
book_id INT ,
book_name VARCHAR(100),
authors VARCHAR(100),
info VARCHAR(100),
comment VARCHAR(100),
year_publication YEAR,
INDEX(year_publication)
);
2.创建唯一索引
CREATE TABLE test1(
id INT NOT NULL,
name varchar(30) NOT NULL,
UNIQUE INDEX uk_idx_id(id)
);
3.主键索引
设定为主键后数据库会自动建立索引,innodb为聚簇索引,语法:
CREATE TABLE student (
id INT UNSIGNED AUTO_INCREMENT ,
student_no VARCHAR(200),
student_name VARCHAR(200),
PRIMARY KEY(id)
);
ALTER TABLE student drop PRIMARY KEY;
修改主键索引:必须先删除掉(drop)原索引,再新建(add)索引
- 如果设置了一个字段为自增,那么这个字段也必须要设置为主键
- 如果需要设置多个主键,其中只能有一个是自增的
4.创建单列索引
引举:
CREATE TABLE test2(
id INT NOT NULL,
name CHAR(50) NULL,
INDEX single_idx_name(name(20))
);
5.创建组合索引
举例:创建表test3,在表中的id、name和age字段上建立组合索引,SQL语句如下:
CREATE TABLE test3(
id INT NOT NULL,
name CHAR(30) NOT NULL,
age INT NOT NULL,
info VARCHAR(255),
INDEX multi_idx(id,name,age)
);
在test3表中,查询id和name字段,使用EXPLAIN语句查看索引的使用情况:
EXPLAIN SELECT * FROM test3 WHERE id=1 AND name='songhongkang';
在test3表中,查询name和age字段或者单独查询name和age字段,使用EXPLAIN语句查看索引的使用情况:
EXPLAIN SELECT * FROM test3 WHERE NAME='songhongkang' AND AGE = 12;
可以看到,查询id和name字段时,使用了名称为MultiIdx的索引,如果查询(name, age)组合或者单独查询name和age字段,会发现结果中possible_keys和key值为NULL,并没有使用在t3表中创建的索引进行查询
6.创建全文索引
FULLTEXT全文索引可以用于全文检索,并且只为CHAR
、VARCHAR
和TEXT
列创建索引。索引总是对整个列进行,不支持局部(前缀)索引。
举例1:创建表test4,在表中的info字段上建立全文索引,SQL语句如下:
CREATE TABLE test4(
id INT NOT NULL,
name CHAR(30) NOT NULL,
age INT NOT NULL,
info VARCHAR(255),
FULLTEXT INDEX futxt_idx_info(info)
);
举例2:创建了一个给title和body字段添加全文索引的表
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR (200),
body TEXT,
FULLTEXT index (title, body)
);
举例3:
CREATE TABLE papers(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(200) DEFAULT NULL,
content TEXT,
PRIMARY KEY (id),
FULLTEXT KEY title(title,content)
);
不同于like方式的的查询:
SELECT * FROM papers WHERE content LIKE ‘%查询字符串%’;
全文索引用match+against方式查询:
SELECT * FROM papers WHERE MATCH(title,content) AGAINST ('查询字符串');
明显的提高查询效率
注意
- 全文索引比 like +% 快 N 倍,但是可能存在精度问题
- 如果需要全文索引的是大量数据,建议先添加数据,再创建索引
在已经存在的表中创建索引可以使用ALTER TABLE语句或者CREATE INDEX语句
1.使用ALTER TABLE语句创建索引
ALTER TABLE table_name ADD
[UNIQUE|FULLTEXT|SPATIAL][INDEX|KEY] [index_name] (col_name[LENGTH],...)[ASC|DESC];
2.使用CREATE INDEX创建索引
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name
ON table_name (col_name[LENGTH],...)[ASC|DESC];
1.使用ALTER TABLE删除索引
ALTER TABLE table_name DROP INDEX index_name;
2.使用DROP INDEX语句删除索引
DROP INDEX index_name ON table_name;
删除表中的列时,如果要删除的列为索引的组成部分,则该列也会从索引中删除。如果组成索引的所有列都被删除,则整个索引将被删除
MySQL 8.x版本才开始真正支持降序索引(仅限于InnoDBc存储引擎)
从MySQL 8.x开始支持隐藏索引(invisible indexes),只需要将待删除的索引设置为隐藏索引,使查询优化器不再使用这个索引,确认将索引设置为隐藏索引后系统不受任何响应,就可以彻底删除索引。这种通过先将索引设置为隐藏索引,再删除索引的方式就是软删除
注意:
- 主键不能被设置为隐藏索引。当表中没有显式主键时,表中第一个唯一非空索引会成为隐式主键,也不能设置为隐藏索引
1.创建表时直接创建
在MySQL中创建隐藏索引通过SQL语句INVISIBLE来实现,其语法形式如下:
CREATE TABLE tablename(
propname1 type1[CONSTRAINT1],
propname2 type2[CONSTRAINT2],
……
propnamen typen,
INDEX [indexname](propname1 [(length)]) INVISIBLE
);
上述语句比普通索引多了一个关键字INVISIBLE,用来标记索引为不可见索引
2.在已经存在的表上创建
可以为已经存在的表设置隐藏索引,其语法形式如下:
CREATE INDEX indexname
ON tablename(propname[(length)]) INVISIBLE;
3.通过ALTER TABLE语句创建
语法形式如下:
ALTER TABLE tablename
ADD INDEX indexname (propname [(length)]) INVISIBLE;
4.切换索引可见状态
已存在的索引可通过如下语句切换可见状态:
ALTER TABLE tablename ALTER INDEX index_name INVISIBLE; #切换成隐藏索引
ALTER TABLE tablename ALTER INDEX index_name VISIBLE; #切换成非隐藏索引
如果将index_cname索引切换成可见状态,通过explain查看执行计划,发现优化器选择了index_cname索引
注意当索引被隐藏时,它的内容仍然是和正常索引一样实时更新的。如果一个索引需要长期被隐藏,那么可以将其删除,因为索引的存在会影响插入、更新和删除的性能。
通过设置隐藏索引的可见性可以查看索引对调优的帮助
5.使隐藏索引对查询优化器可见
在MySQL 8.x版本中,为索引提供了一种新的测试方式,可以通过查询优化器的一个开关(use_invisible_indexes)来打开某个设置,使隐藏索引对查询优化器可见。如果use_invisible_indexes 设置为off (默认),优化器会忽略隐藏索引。如果设置为on,即使隐藏索引不可见,优化器在生成执行计划时仍会考虑使用隐藏索引
(1)在MySQL命令行执行如下命令查看查询优化器的开关设置
select @@optimizer_switch \G
在输出的结果信息中找到如下属性配置
use_invisible_indexes=off
此属性配置值为off,说明隐藏索引默认对查询优化器不可见
(2)使隐藏索引对查询优化器可见,需要在MySQL命令行执行如下命令:
set session optimizer_switch="use_invisible_indexes=on";
Query OK,0 rows affected (0.00 sec)
SQL语句执行成功,再次查看查询优化器的开关设置
select @@optimizer_switch ;
***************************1. row ***************************
@@optimizer_switch:
index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_
intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_co
st_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on
,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on
,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on,use_invisible_ind
exes=on,skip_scan=on,hash_join=on
1 row in set (0.00 sec)
此时,在输出结果中可以看到如下属性配置
use_invisible_indexes=on
use_invisible_indexes属性的值为on,说明此时隐藏索引对查询优化器可见
(3)使用EXPLAIN查看以字段invisible_column作为查询条件时的索引使用情况
explain select * from classes where cname = '高一2班';
查询优化器会使用隐藏索引来查询数据
(4)如果需要使隐藏索引对查询优化器不可见,则只需要执行如下命令即可
set session optimizer_switch="use_invisible_indexes=off";
Query OK,0 rows affected (0.00 sec)
再次查看查询优化器的开关设置
select @@optimizer_switch ;
此时,use_invisible_indexes属性的值已经被设置为“off”
第1步:创建数据库、创建表
CREATE DATABASE mysqlPlus;
USE mysqlPlus;
#1.创建学生表和课程表
CREATE TABLE student_info(
id INT NOT NULL AUTO_INCREMENT,
student_id INT NOT NULL ,
name VARCHAR(20) DEFAULT NULL,
course_id INT NOT NULL ,
class_id INT DEFAULT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
)AUTO_INCREMENT=1;
CREATE TABLE course(
id INT NOT NULL AUTO_INCREMENT,
course_id INT NOT NULL ,
course_name VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (id)
)AUTO_INCREMENT=1;
第2步:创建模拟数据必需的存储函数
#函数1:创建随机产生字符串函数
DELIMITER //
CREATE FUNCTION rand_string(n INT)
RETURNS VARCHAR(255)#该函数会返回一个字符串
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i +1;
END WHILE;
RETURN return_str;
END //
DELIMITER ;
#函数2:创建随机数函数
DELIMITER //
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1));
RETURN i;
END //
DELIMITER ;
创建函数,假如报错:
This function has none of DETERMINISTIC......
由于开启过慢查询日志bin-log,我们就必须为我们的function指定一个参数。
主从复制,主机会将写操作记录在bin-log日志中。从机读取bin-log日志,执行语句来同步数据。如果使用函数来操作数据,会导致从机和主键操作时间不一致。所以,默认情况下,mysql不开启创建函数设置。
show variables like 'log_bin_trust_function_creators';
set global log_bin_trust_function_creators=1; #不加global只是当前窗口有效。
mysqld重启,上述参数又会消失。永久方法:
windows下:my.ini[mysqld]加上:
log_bin_trust_function_creators=1
linux下:vim /etc/my.cnf
中[mysqld]加上:
log_bin_trust_function_creators=1
第3步:创建插入模拟数据的存储过程
#存储过程1:创建插入课程表存储过程
DELIMITER //
CREATE PROCEDURE insert_course( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i = i +1; #赋值
INSERT INTO course (course_id, course_name ) VALUES
(rand_num(10000,10100),rand_string(6));
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END //
DELIMITER ;
#存储过程2:创建插入学生信息表存储过程
DELIMITER //
CREATE PROCEDURE insert_stu( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i = i +1; #赋值
INSERT INTO student_info (course_id, class_id ,student_id ,NAME ) VALUES
(rand_num(10000,10100),rand_num(10000,10200),rand_num(1,200000),rand_string(6));
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END //
DELIMITER ;
第4步:调用存储过程
CALL insert_course(100);
CALL insert_stu(1000000);
例如,在student表中,字段id具有唯一性,可以为该字段创建唯一索引,可以快速的确定某个学生的信息
注意:
- 只要为字段创建了索引,当查询该字段时,查询结果会自动按照升序或者降序的顺序展示
首先,连接表的数量尽量不要超过3 张
其次,对 WHERE 条件创建索引,因为 WHERE 才是对数据条件的过滤
最后,对用于连接的字段创建索引,并且该字段在多张表中的类型必须一致
create table shop(address varchar(120) not null);
alter table shop add index(address(12));
由于索引需要占用磁盘空间、影响INSERT、DELETE、UPDATE等性能…所以一般一张表中最多设置6个索引
流程划分成了**观察(Show status)和行动(Action)**两个部分
字母 S 的部分代表观察(会使用相应的分析工具),字母 A 代表的部分是行动(对应分析可以采取的行动)
采用分析工具可以帮助定位到有问题的SQL,这三种分析工具即为SQL调优的三个步骤:慢查询、EXPLAIN、SHOWPROFILING
使用SHOW STATUS
语句查询一些MySQL数据库服务器的性能参数、执行频率
SHOW [GLOBAL|SESSION] STATUS LIKE '参数';
一些常用的性能参数如下:
评价一个查询的执行效率的一个常用指标:last_query_cost
,对应的是SQL 语句所需要读取的读页的数量
SHOW STATUS LIKE 'last_query_cost';
使用场景:
SQL查询是一个动态的过程,从页加载的角度来看:
位置决定效率
。如果页就在数据库缓冲池
中,那么效率是最高的,否则还需要从内存
或者磁盘
中进行读取,针对单个页的读取来说,如果页存在于内存中,会比在磁盘中读取效率高很多批量决定效率
。如果从磁盘中对单一页进行随机读,那么效率是很低的,而采用顺序读取的方式,批量对页进行读取,平均一页的读取效率就会提升很多,甚至要快于单个页面在内存中的随机读取所以首先要考虑数据存放的位置,如果是进程使用的数据就要尽量放到
缓冲池
中,其次可以充分利用磁盘的吞吐能力,一次性批量读取数据,这样单个页的读取效率也就得到了提升
慢查询日志用来记录MySQL中响应时间超过阈值的语句,具体运行时间超过long_query_time
值的SQL,则会被记录在慢查询日志中,long_query_time
默认值为10(运行10秒以上的语句)
主要作用:帮助发现执行时间特别长的SQL查询,并且有针对性的进行优化,从而提高系统的整体效率。当数据库服务器发生阻塞、运行变慢等情况时,可以通过检查慢查询日志,找到慢查询语句,一般一条sql语句超过5秒即为慢查询。可以通过结合explain语句收集超过5秒的sql进行全面分析
默认情况下MySQL数据库没有开启慢查询日志,当需要调优时再开启即可
查看慢查询日志是否开启
mysql > show variables like '%slow_query_log';
显示慢查询日志信息
SHOW VARIABLES LIKE `slow_query_log%`;
开启慢查询日志
set global slow_query_log='ON';
查看慢查询的时间阈值
show variables like '%long_query_time%';
临时性设置慢查询的时间阈值
set global long_query_time = 1;
永久设置慢查询的时间阈值
修改配置文件vim /etc/my.cf
[mysqld]
slow_query_log=ON #开启慢查询日志开关
slow_query_log_file=/var/lib/mysql/atguigu-low.log #慢查询日志的目录和文件名信息
long_query_time=3 #设置慢查询的阈值为3秒,超出此设定值的SQL即被记录到慢查询日志
log_output=FILE
注意:修改慢查询是否开启设置后,需要重启mysql服务器
systemctl restart mysqld.service
SHOW GLOBAL STATUS LIKE '%Slow_queries%';
CREATE TABLE `student`(
`id` INT NOT NULL AUTO_INCREMENT,
`stuno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT DEFAULT NULL,
`classId` INT DEFAULT NULL,
PRIMARY KEY (`id`)
)AUTO_INCREMENT=1;
1.设置参数 log_bin_trust_function_creators
set global log_bin_trust_function_creators=1; #不加global只是当前窗口有效。
2.创建函数
随机产生字符串
DELIMITER //
CREATE FUNCTION rand_string(n INT)
RETURNS VARCHAR(255)#该函数会返回一个字符串
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i +1;
END WHILE;
RETURN return_str;
END //
DELIMITER ;
#测试
SELECT rand_string(10);
产生随机数值
DELIMITER //
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1));
RETURN i;
END //
DELIMITER ;
#测试:
SELECT rand_num(10,100);
3.创建存储过程
DELIMITER //
CREATE PROCEDURE insert_stu1( START INT , max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i = i +1; #赋值
INSERT INTO student (stuno, NAME ,age ,classId ) VALUES
((START+i),rand_string(6),rand_num(10,100),rand_num(10,1000));
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END //
DELIMITER ;
4.调用存储过程
#调用刚刚写好的函数,40000条记录,从100001号开始
CALL insert_stu1(100001,40000);
5.测试及分析
测试
SELECT * FROM student WHERE stuno = 100760;
SELECT * FROM student WHERE NAME = 'WkjrhJ';
分析
show status like 'slow_queries';
mysqldumpslow --help
mysqldumpslow 命令的具体参数:
eg:按照查询时间排序,查看前五条 SQL 语句
mysqldumpslow -s t -t 5 /var/lib/mysql/ablelynn100-slow.log
临时性关闭
SET GLOBAL slow_query_log=off;
永久性关闭
[mysqld]
slow_query_log=OFF
注意:修改慢查询是否开启设置后,需要重启mysql服务器
systemctl restart mysqld.service
显示慢查询日志信息
SHOW VARIABLES LIKE 'slow_query_log%'
得到慢查询日志的目录默认为MySQL的数据目录,手动删除慢查询日志文件
使用命令mysqladmin flush-logs
来重新生成查询日志文件,执行完毕会在数据目录下重新生成慢查询日志文件
mysqladmin -uroot -p flush-logs slow
提示
慢查询日志都是使用mysqladmin flush-logs命令来删除重建的。使用时一定要注意,一旦执行了这个命令,慢查询日志都只存在新的日志文件中,如果需要旧的查询日志,就必须事先备份
show profile
是 MySQL 提供的可以用来分析当前会话中 SQL 都做了什么、执行的资源消耗工具的情况,可用于 sql 调优的测量。默认情况下处于关闭状态,并保存最近15次的运行结果
show variables like 'profiling';
set profiling = 'ON';
相关查询执行后,查看当前会话都有哪些profiles: show profiles;
查询最近一次查询的开销:show profile;
show profile的常用查询参数:
ALL:显示所有的开销信息
BLOCK IO:显示块IO开销
CONTEXT SWITCHES:上下文切换开销
CPU:显示CPU开销信息
IPC:显示发送和接收开销信息
MEMORY:显示内存开销信息
PAGE FAULTS:显示页面错误开销信息
SOURCE:显示和Source_function,Source_file, Source_line相关的开销信息
SWAPS:显示交换次数开销信息
日常开发需注意的结论:
converting HEAP to MyISAM
: 查询结果太大,内存不够,数据往磁盘上搬了Creating tmp table
:创建临时表。先拷贝数据到临时表,用完后再删除临时表Copying to tmp table on disk
:把内存中临时表复制到磁盘上,警惕locked
如果在show profile诊断结果中出现了以上4条结果中的任何一条,则sql语句需要优化。
当定位了查询慢的sql后,可以使用EXPLAIN
或DEXCRIBE
工具做针对性的分析查询语句(EXPLAIN
或DEXCRIBE
功能一样)
EXPLAIN
语句帮助查看某个查询语句的具体执行计划
可以得到:
EXPLAIN 或 DESCRIBE语句的语法形式如下:
EXPLAIN SELECT select_options
或者
DESCRIBE SELECT select_options
如果想看看某个查询的执行计划,可以在具体的查询语句前边加一个 EXPLAIN
EXPLAIN SELECT 1;
列名 | 描述 |
---|---|
id | 在一个大的查询语句中每个select关键字对应一个唯一的id |
select_type | select关键字对应的那个查询的类型 |
table | 表名 |
partitions | 匹配的分区信息 |
type | 针对单表的访问方法 |
possible_keys | 可能用到的索引 |
key | 实际用到的索引 |
key_len | 实际用到的索引长度 |
ref | 当时用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows | 预估的需要读取的记录条数 |
filtered | 某个表经过搜索条件过滤后剩余记录条数的百分比 |
extra | 一些额外信息 |
CREATE TABLE s1 (
id INT AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
INDEX idx_key1 (key1),
UNIQUE INDEX idx_key2 (key2),
INDEX idx_key3 (key3),
INDEX idx_key_part(key_part1, key_part2, key_part3)
);
CREATE TABLE s2 (
id INT AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
INDEX idx_key1 (key1),
UNIQUE INDEX idx_key2 (key2),
INDEX idx_key3 (key3),
INDEX idx_key_part(key_part1, key_part2, key_part3)
);
设置参数 log_bin_trust_function_creators,保证允许创建函数
set global log_bin_trust_function_creators=1;
创建函数
DELIMITER //
CREATE FUNCTION rand_string1(n INT)
RETURNS VARCHAR(255)#该函数会返回一个字符串
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i +1;
END WHILE;
RETURN return_str;
END //
DELIMITER ;
创建存储过程
创建往s1表中插入数据的存储过程:
DELIMITER //
CREATE PROCEDURE insert_s1 (IN min_num INT,IN max_num INT)
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i +1;
INSERT INTO s1 VALUES(
(min_num + i),
rand_string1(6),
(min_num +30 * i +5),
rand_string1(6),
rand_string1(10),
rand_string1(5),
rand_string1(10),
rand_string1(10));
UNTIL i = max_num
END REPEAT;
COMMIT;
END //
DELIMITER ;
创建往s2表中插入数据的存储过程:
DELIMITER //
CREATE PROCEDURE insert_s2 (IN min_num INT ,IN max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i +1;
INSERT INTO s2 VALUES(
(min_num + i),
rand_string1(6),
(min_num +30 * i +5),
rand_string1(6),
rand_string1(10),
rand_string1(5),
rand_string1(10),
rand_string1(10));
UNTIL i = max_num
END REPEAT;
COMMIT;
END //
DELIMITER ;
调用存储过程
s1表数据的添加:加入1万条记录:
CALL insert_s1(10001,10000);
s2表数据的添加:加入1万条记录:
CALL insert_s2(10001,10000);
MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该表的表名(有时不是真实的表名字,可能是简称)
单表查询:
EXPLAIN SELECT * FROM s1;
连接查询:
EXPLAIN SELECT * FROM s1 INNER JOIN s2;
多表查询,会显示所有用到的表,其id值都相同,extra显示使用到的查询方法
在连接查询的执行计划中,每个表都会对应一条记录,这些记录的id列的值是相同的,出现在前边的表表示驱动表,出现在后面的表表示被驱动表
EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
注意:每出现一个select关键字,就会为此分配一个新的id值,查询优化器会对涉及子查询的查询语句重写,转换为连接查询,所以会出现不同的id值,但如果是连接查询,则只会出现一个id值
EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field = 'a');
小结:
MySQL为每一个SELECT
关键字代表的小查询定义了一个select_type
属性,可以通过select_type
属性了解小查询在大查询中的作用
名称 | 描述 |
---|---|
SIMPLE | 简单的查询(没有使用UNION 或者子查询(subqueries)) |
PRIMARY | 主键查询 |
UNION | 在UNION中第二个或者更后面的查询语句 |
UNION RESULT | UNION的结果 |
SUBQUERY | 子查询中的第一个查询 |
DEPENDENT SUBQUERY | 子查询中第一个查询,依赖外部查询 |
DEPENDENT UNION | 在UNION中第二个或者更后面的查询语句,依赖外部查询 |
DERIVED | 驱动表 |
MATERIALIZED | Mateerialized 子查询 |
UNCACHEABLE SUBQUERY | 子查询的结果不能被缓存,必须被用于外部查询的计算 |
UNCACHEABLE UNION | UNION的第二个或者更后面的查询,数据不能被缓存 |
UNION
或者子查询的查询都算作是SIMPLE
类型UNION、UNION ALL
或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的select_type
的值就是PRIMARY
UNION
或者UNION ALL
的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询意外,其余的小查询的select_type
值就是UNIONUNION
查询的去重工作,针对该临时表的查询的select_type
就是UNION RESULT
SELECT
关键字代表的那个查询的select_type
就是SUBQUERY
代表分区表中的命中情况,非分区表,该项为NULL
。一般情况下查询语句的执行计划的partitions
列的值为NULL
type
列就表明了这个访问方法(system , const , eq_ref , ref , fulltext , ref_or_null , index_merge , unique_subquery , index_subquery , range , index , ALL
)
system
:当表中只有一条记录
并且该表使用的存储引擎的统计数据是精确的,比如MyISAM引擎,那么对该表的访问方法就是system
const
:当根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const
eq_ref
:在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较)。则对该被驱动表的访问方法就是eq_ref
ref
:当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref
fulltext
:全文索引ref_or_null
:当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL
值时,那么对该表的访问方法就可能是ref_or_null
index_merge
:单表访问方法时在某些场景下可以使用Interseation、union、Sort-Union
这三种索引合并的方式来执行查询unique_subquery
:unique_subquery
是针对在一些包含IN
子查询的查询语句中,如果查询优化器决定将IN
子查询转换为EXISTS
子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的type
列的值就是unique_subquery
index_subquery
:index_subquery
是针对在一些包含IN
子查询的查询语句中,如果查询优化器决定将IN
子查询转换为EXISTS
子查询,而且子查询可以使用到索引进行等值匹配的话,那么该子查询执行计划的type
列的值就是index_subquery
range
:查询是范围查询时index
:需要扫描所有索引记录时ALL
:全表查询结果值从最好到最坏依次是:
- system > const > eq_ref > ref> fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
- SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。(阿里巴巴开发手册要求)
possible_keys
列表示在某个查询语句中,对某个列执行单表查询时可能用到的索引有哪些,一般查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。key
列表示实际用到的索引有哪些,如果为NULL,则没有使用索引
实际使用到的索引长度,帮帮助检查是否充分的利用了索引
,值越大越好
显示索引的哪一列被使用,哪些列或常量被用于查找索引列上的值
预估的需要读取的记录条数,值越小越好
某个表经过搜索条件过滤后剩余记录条数的百分比
包含不适合在其他列中显示但十分重要的额外信息。我们可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句
No tables used
:查询语句没有FROM
子句时将会提示该额外信息Impossible WHERE
:查询语句的WHERE
子句永远为FALSE
时将会提示该额外信息Using where
:使用全表扫描对某个表的查询,查询语句的WHERE
子句中有针对该表的搜索条件时将会提示该额外信息No matching min/max row
:当查询列表处有MIN
或者MAX
聚合函数,但是并没有符合WHERE
子句中的搜索条件的记录时Using index
:查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用覆盖索引的情况下,在Extra
列将会提示该额外信息Using index condition
:有些搜索条件中虽然出现了索引列,但却不能使用到索引Using join buffer (Block Nested Loop)
:在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫join buffer
的内存块来加快查询速度Not exists
:使用左(外)连接时,如果WHERE
子句中包含要求被驱动表的某个列等于NULL
值的搜索条件,而且那个列是不允许存储NULL
值的,那么在该表的执行计划的Extra列就会提示这个信息Using intersect(...)、 Using union(...)和 Using sort_union(...)
:
Using intersect(...)
提示,说明准备使用Intersect
索引合并的方式执行查询,括号中的...
表示需要进行索引合并的索引名称Using union(...)
提示,说明准备使用Union
索引合并的方式执行查询Using sort_union(...)
提示,说明准备使用Sort-Union
索引合并的方式执行查询Zero limit
:当我们的LIMIT
子句的参数为0
时,表示压根儿不打算从表中读取任何记录,将会提示该额外信息Using filesort
:有一些情况下对结果集中的记录进行排序是可以使用到索引的EXPLAIN可以输出四种格式:传统格式
,JSON格式
,TREE格式
以及可视化输出
传统格式简单明了,输出是一个表格形式,概要说明查询计划
JSON格式是四种格式里面输出信息最详尽
的格式,里面包含了执行的成本信息
主要根据查询的各个部分之间的关系
和各部分的执行顺序
来描述如何查询
可视化输出,可以通过MySQL Workbench可视化查看MySQL的执行计划。通过点击Workbench的放大镜图标,即可生成可视化的查询计划
可以跟踪优化器做出的各种决策,并将结果记录到INFORMATION_SCHEMA.OPTIMIZER_TRACE表中
默认关闭,开始时设置格式为json,同时设置trace最大能够使用的内存大小
SET optimizer_trace="enabled=on",end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;
一般建议
- 全值匹配
- 最佳左前缀法则
- 主键设置自增
where
条件中is null可以使用索引,is not null 无法使用索引- 在使用LIKE关键字进行查询的查询语句中,如果匹配字符串的第一个字符为’%‘,索引就不会起作用。只有’%'不在第一个位置,索引才会起作用
- 在WHERE子句中,如果在OR前的条件列进行了索引,而在OR后的条件列没有进行索引,那么索引会失效。也就是说,OR前后的两个条件中的列都是索引时,查询中才使用索引
- 数据库和表的字符集统一使用utf8mb4
LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引
。
inner join对于内连接来说,查询优化器可以决定谁作为驱动表,谁作为被驱动表出现的
子查询的执行效率不高,原因:
执行子查询时,MySQL需要为内层查询语句的查询结果建立一个临时表,然后外层查询语句从临时表中查询记录。查询完毕后,再撤销这些临时表。这样会消耗过多的CPU和IO资源,产生大量的慢查询
子查询的结果集存储的临时表,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响
对于返回结果集比较大的子查询,其对查询性能的影响也就越大
**在MySQL中,可以使用连接(JOIN)查询来替代子查询。**连接查询不需要建立临时表
,其速度比子查询
要快,如果查询中使用索引的话,性能就会更好
优化建议:
避免全表扫描
,在 ORDER BY 子句避免使用 FileSort 排序
。当然,某些情况下全表扫描,或者 FileSort 排序不一定比索引慢优化思路
索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了满足查询结果的数据就叫做覆盖索引
MySQL是支持前缀索引的。默认地,如果创建索引的语句不指定前缀长度,那么索引就会包含整个字符串,一般当字符串很长时,在建立字符串索引是设置前一小部分为索引即可
唯一索引和普通所用在效率方面几乎没区别
选择的标准:与表的大小有关,如果EXISTS、IN
前的表大,则使用IN
,如果EXISTS、IN
前的表小,则使用EXISTS
一般采用全表扫描的方式统计数据表的行数,select count(*)
一般不建议使用SELECT(*)
针对的是会扫描全表的 SQL 语句,如果可以确定结果集只有一条,那么加上 LIMIT 1
后,当找到一条结果的时候就不会继续扫描,这样会加快查询速度
多使用 COMMIT,这样程序的性能得到提高,需求也会因为 COMMIT 所释放的资源而减少。
COMMIT 所释放的资源:
在关系型数据库中,关于数据表设计的基本原则、规则就称为范式
目前关系型数据库有六种常见范式,按照范式级别,从低到高分别是:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)
范式的定义会使用到主键和候选键
第一范式主要确保数据库中每个字段的值必须具有原子性
,也就是说数据表中每个字段的值为不可再次拆分
的最小数据单元
第二范式要求,在满足第一范式的基础上,满足数据库里的每一条数据记录,都是可唯一标识的。而且所有非主键字段,都必须完全依赖主键,不能只依赖主键的一部分。如果知道主键的所有属性的值,就可以检索到任何元组(行)的任何属性的任何值
举例:
成绩表
(学号,课程号,成绩)关系中,(学号,课程号)可以决定成绩,但是学号不能决定成绩,课程号也不能决定成绩,所以“(学号,课程号)→成绩”就是完全依赖关系
第三范式是在第二范式的基础上,确保数据表中的每一个非主键字段都和主键字段直接相关,也就是说,要求数据表中的所有非主键字段不能依赖于其他非主键字段。(即,不能存在非主属性A依赖于非主属性B,非主属性B依赖于主键C的情况,即存在“A->B->C"的决定关系)通俗地讲,该规则的意思是所有非主键属性
之间不能由依赖关系,必须相互独立
必须遵循:
- 第一范式:确保每列保持原子性,每列不可分割
- 第二范式:确保每列与主键完全依懒
- 第三范式:确保每列和主键直接相关,而不是间接相关
遵循业务优先的原则,即首先满足业务需求,然后满足范式要求
规范化 vs 性能
- 为满足某种商业目标,数据库性能比规范化数据库更重要
- 在数据规范化的同时,要综合考虑数据库的性能
- 通过在给定的表中添加额外的字段,以大量减少需要从中搜索信息所需的时间
- 通过在给定的表中插入计算列,以方便查询
当冗余信息有价值或者能大幅度提高查询效率
的时候,才会采取反范式的优化
增加冗余字段一定要符合如下两个条件。只要满足这两个条件,才可以考虑增加夯余字段
1)这个冗余字段不需要经常进行修改
2)这个冗余字段查询的时候不可或缺
在现实生活中,经常需要一些冗余信息,比如订单中的收货人信息,包括姓名、电话和地址等。每次发生的订单收货信息
都属于历史快照
,需要进行保存,但用户可以随时修改自己的信息,这时保存这些冗余信息是非常有必要的
反范式优化也常用在数据仓库
的设计中,因为数据仓库通常存储历史数据
,对增删改的实时性要求不强,对历史数据的分析需求强。这时适当允许数据的冗余度,更方便进行数据分析
数据仓库和数据库在使用上的区别:
捕捉数据
,而数据仓库设计的目的在于分析数据
增删改实时性
要求强,需要存储在线的用户数据,而数据仓库存储的一般是历史数据
尽量避免冗余
,但为了提高查询效率也允许一定的冗余度
,而数据仓库在设计上更偏向采用反范式设计在3NF的基础上进行了改进,提出了巴斯范式(BCNF),页脚巴斯-科德范式(Boyce - Codd Normal Form)。BCNF被认为没有新的设计规范加入,只是对第三范式中设计规范要求更强,使得数据库冗余度更小。所以,称为是修正的第三范式
,或扩充的第三范式
,BCNF不被称为第四范式
若一个关系达到了第三范式,并且它只有一个候选键,或者它的每个候选键都是单属性,则该关系自然达到BC范式
一般来说,一个数据库设符合3NF或者BCNF就可以了
商超进货系统中的进货单表
进行剖析:
进货单表:
这个表中的字段很多,表里的数据量也很惊人。大量重复导致表变得庞大,效率极低
在实际工作场景中,这种由于数据表结构设计不合理,而导致的数据重复的现象并不少见。往往是系统虽然能够运行,承载能力却很差,稍微有点流量,就会出现内存不足、CPU使用率飙升的情况,甚至会导致整个项目失败
第一范式要求:所有的字段都是基本数据类型,不可进行拆分。这里需要确认,所有的列中,每个字段只包含一种数据。
这张表里,把“property"这一字段,拆分成”specification (规格)“和"unit (单位)”,这两个字段如下:
第二范式要求,在满足第一范式的基础上,还要满足数据表里的每一条数据记录,都是可唯一标识的。而且所有字段,都必须完全依赖主键,不能只依赖主键的一部分
第1步,就是要确定这个表的主键。通过观察发现,字段“listnumber(单号)"+"barcode(条码)"可以唯一标识每一条记录,可以作为主键
第2步,确定好了主键以后,判断哪些字段完全依赖主键,哪些字段只依赖于主键的一部分。把只依赖于主键一部分的字段拆出去,形成新的数据表
首先,进货单明细表里面的"goodsname(名称)““specification(规格)”“unit(单位)“这些信息是商品的属性,只依赖于"batcode(条码)”,不完全依赖主键,可以拆分出去。把这3个字段加上它们所依赖的字段"barcode(条码)”,拆分形成新的数据表"商品信息表”
这样一来,原来的数据表就被拆分成了两个表
商品信息表:
进货单表:
此外,字段"supplierid(供应商编号)““suppliername(供应商名称)”“stock(仓库)“只依赖于"listnumber(单号)”,不完全依赖于主键,所以,可以把"supplierid”“suppliername”“stock"这3个字段拆出去,再加上它们依赖的字段"listnumber(单号)”,就形成了一个新的表"进货单头表"剩下的字段,会组成新的表,我们叫它"进货单明细表"
原来的数据表就拆分成了3个表
进货单头表:
进货单明细表:
商品信息表:
第3步,在“商品信息表”中,字段“barcode"是有可能存在重复
的,比如,用户门店可能有散装称重商品和自产商品,会存在条码共用的情况。所以,所有的字段都不能唯一标识表里的记录。这个时候,必须给这个表加上一个主键,比如说是自增字段"itemnumber"
进货单头表,还有数据冗余的可能。因为"suppliername"依赖"supplierid",那么就可以按照第三范式的原则进行拆分了。进一步拆分进货单头表,把它拆解陈供货商表和进货单头表
供货商表:
进货单头表:
这2个表都满足第三范式的要求了
ER模型也称实体关系模型
ER 模型中有三个要素,分别是实体、属性和关系
实体
,数据对象,往往对应于现实生活中的真实存在的个体。在 ER 模型中,用矩形来表示。实体分为两类,分别是强实体和弱实体。强实体是指不依赖于其他实体的实体;弱实体是指对另一个实体有很强的依赖关系的实体
属性
,指实体的特性。比如超市的地址、联系电话、员工数等。在 ER 模型中用椭圆形来表示
关系
,指实体之间的联系。比如超市把商品卖给顾客,就是一种超市与顾客之间的联系。在 ER 模型中用菱形来表示
注意:实体和属性不容易区分。这里提供一个原则:要从系统整体的角度出发去看,可以独立存在的是实体,不可再分的是属性。也就是说,属性不能包含其他属性
在 ER 模型的3 个要素中,关系又可以分为3 种类型,分别是一对一、一对多、多对多
一对一
:指实体之间的关系是一一对应的,比如个人与身份证信息之间的关系就是一对一的关系。一个人只能有一个身份证信息,一个身份证信息也只属于一个人
一对多
:指一边的实体通过关系,可以对应多个另外一边的实体。相反,另外一边的实体通过这个关系,则只能对应唯一的一边的实体。比如说,我们新建一个班级表,而每个班级都有多个学生,每个学生则对应一个班级,班级对学生就是一对多的关系
多对多
:指关系两边的实体都可以通过关系对应多个对方的实体。比如在进货模块中,供货商与超市之间的关系就是多对多的关系,一个供货商可以给多个超市供货,一个超市也可以从多个供货商那里采购商品。再比如一个选课表,有许多科目,每个科目有很多学生选,而每个学生又可以选择多个科目,这就是多对多的关系
“三少一多”
CREATE DATABASE xxx DEFAULT CHARACTER SET 'utf8';
【强制】表和列的名称必须控制在32个字符以内,表名只能使用英文字母、数字和下划线,建议以英文字母开头
【强制】表名、列名一律小写,不同单词采用下划线分割。须见名知意
【强制】表名要求有模块名强相关,同一模块的表名尽量使用统一前缀。比如:crm_fund_item
【强制】创建表时必须显式指定字符集为utf8或utf8mb4
【强制】表名、列名禁止使用关键字
【强制】创建表时必须显式指定表存储引擎类型。如无特殊需求,一律为InnoDB
【强制】建表必须有comment
【强制】字段命名应尽可能使用表达实际含义的英文单词或缩写。如:公司 ID,不要使用 corporation_id,而用corp_id
【强制】布尔值类型的字段命名为 is_描述。如member表上表示是否为enabled的会员的字段命名为 is_enabled
【强制】禁止在数据库中存储图片、文件等大的二进制数据,通常文件很大,短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机IO操作,文件很大时,IO操作很耗时。通常存储于文件服务器,数据库只存储文件地址信息
【建议】建表时关于主键:表必须有主键
【建议】核心表(如用户表)必须有行数据的创建时间字段(create_time)和最后更新时间字段(update_time),便于查问题
【建议】表中所有字段尽量都是 NOT NULL 属性,业务可以根据需要定义 DEFAULT值。因为使用 NULL值会存在每一行都会占用额外存储空间、数据迁移容易出错、聚合函数计算结果偏差等问题
【建议】所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)
【建议】中间表(或临时表)用于保留中间结果集,名称以 tmp_ 开头。备份表用于备份或抓取源表快照,名称以 bak_ 开头。中间表和备份表定期清理
【示范】一个较为规范的建表语句:
CREATE TABLE user_info (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`user_id` bigint(11) NOT NULL COMMENT '用户id',
`username` varchar(45) NOT NULL COMMENT '真实姓名',
`email` varchar(30) NOT NULL COMMENT '用户邮箱',
`nickname` varchar(45) NOT NULL COMMENT '昵称',
`birthday` date NOT NULL COMMENT '生日',
`sex` tinyint(4) DEFAULT '0' COMMENT '性别',
`short_introduce` varchar(150) DEFAULT NULL COMMENT '一句话介绍自己,最多50个汉字',
`user_resume` varchar(300) NOT NULL COMMENT '用户提交的简历存放地址',
`user_register_ip` int NOT NULL COMMENT '用户注册时的源ip',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP COMMENT '修改时间',
`user_review_status` tinyint NOT NULL COMMENT '用户资料审核状态,1为通过,2为审核中,3为未
通过,4为还未提交审核',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_id`(`user_id`),
KEY `idx_username`(`username`),
KEY `idx_create_time_status`(`create_time`,`user_review_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='网站用户基本信息
PowerDesigner是一款开发人员常用的数据库建模工具,用户利用该软件可以方便地制作数据流程图
、概念数据模型
、物理数据模型
,它几乎包括了数据库模型设计的全过程,是Sybase公司为企业建模和设计提供的一套完整的集成化企业级建模解决方案
my.cnf的参考配置
[mysqld]
port = 3306
serverid = 1
socket = /tmp/mysql.sock
skip-locking #避免MySQL的外部锁定,减少出错几率增强稳定性。
skip-name-resolve #禁止MySQL对外部连接进行DNS解析,使用这一选项可以消除MySQL进行DNS解析的时间。但需要注意,如果开启该选项,则所有远程主机连接授权都要使用IP地址方式,否则MySQL将无法正常处理连接请求!
back_log = 384
key_buffer_size = 256M
max_allowed_packet = 4M
thread_stack = 256K
table_cache = 128K
sort_buffer_size = 6M
read_buffer_size = 4M
read_rnd_buffer_size=16M
join_buffer_size = 8M
myisam_sort_buffer_size =64M
table_cache = 512
thread_cache_size = 64
query_cache_size = 64M
tmp_table_size = 256M
max_connections = 768
max_connect_errors = 10000000
wait_timeout = 10
thread_concurrency = 8 #该参数取值为服务器逻辑CPU数量*2,在本例中,服务器有2颗物理CPU,而每颗物理CPU又支持H.T超线程,所以实际取值为4*2=8
skip-networking #开启该选项可以彻底关闭MySQL的TCP/IP连接方式,如果WEB服务器是以远程连接的方式访问MySQL数据库服务器则不要开启该选项!否则将无法正常连接!
table_cache=1024
innodb_additional_mem_pool_size=4M #默认为2M
innodb_flush_log_at_trx_commit=1
innodb_log_buffer_size=2M #默认为1M
innodb_thread_concurrency=8 #你的服务器CPU有几个就设置为几。建议用默认一般为8
tmp_table_size=64M #默认为16M,调到64-256最挂
thread_cache_size=120
query_cache_size=32M