超级重要的SQL优化问题(下)

网友投稿 766 2022-05-29

本文大纲

前面我已经带着大家学习了本文的第1-4个部分,今天就带大家学习这剩下的5-8个部分。MySQL优化问题对于新手学习,一般是个难题!我的教程自认为已经是很通俗易懂的。如果你学习了这个教程后,仍然不太理解,可以去B站找到一个视频浏览一遍,然后再回头看我的文章。

讲解使用的数据源

在上篇最后,我们已经给出了本文需要使用到的数据代码,这里我直接给出这3张表的图示。

5. explain执行计划常用关键字详解

# 查看执行计划 explain select t.* from teacher t,course c,teacherCard tc where t.tid = c.tid and t.tcid = tc.tcid and (c.cid = 2 or tc.tcid = 3);

结果如下:

接着,在往teacher表中增加几条数据。

insert into teacher values(4,'ta',4); insert into teacher values(5,'tb',5); insert into teacher values(6,'tc',6);

再次查看执行计划。

# 查看执行计划 explain select t.* from teacher t,course c,teacherCard tc where t.tid = c.tid and t.tcid = tc.tcid and (c.cid = 2 or tc.tcid = 3);

结果如下:

这里先记住一句话:表的执行顺序 ,因表数量改变而改变的原因:笛卡尔积。怎么回事呢?看看下面这个例子。

# 下面举一个例子 a b c 2 3 4 最终:2 * 3 * 4 = 6 * 4 = 24 c b a 4 3 2 最终:4 * 3 * 2 = 12 * 2 = 24

分析:最终执行的条数,虽然是一致的。但是中间过程,有一张临时表是6,一张临时表是12,很明显6 < 12,对于内存来说,数据量越小越好,因此优化器肯定会选择第一种执行顺序。

结论:id值相同,从上往下顺序执行。表的执行顺序因表数量的改变而改变,数量越小,越在前面执行。

# 查看执行计划 explain select tc.tcdesc from teacherCard tc where tc.tcid = ( select t.tcid from teacher t where t.tid = (select c.tid from course c where c.cname = 'sql') );

结果如下:

结论:id值不同,id值越大越优先查询。这是由于在进行嵌套子查询时,先查内层,再查外层。

# 查看执行计划 explain select t.tname ,tc.tcdesc from teacher t,teacherCard tc where t.tcid= tc.tcid and t.tid = (select c.tid from course c where cname = 'sql') ;

结果如下:

结论:id值有相同,又有不同。id值越大越优先;id值相同,从上往下顺序执行。

select_type关键字共有如下常用的6种类型,下面我分别带着大家梳理一下,它们各自的含义。

不包含子查询,不包含union查询。

explain select * from teacher;

结果如下:

关于primary和subquery,我们就拿下面的这个例子进行演示。从代码中可以看到这个SQL语句是存在子查询的,换句话说,这个SQL语句包含子查询。where内层(非最外层)使用到的c表属于非最外层,因此是subquery关键字。where外层使用到了t表 和tc表,因此是primary关键字。

# 查看执行计划 explain select t.tname ,tc.tcdesc from teacher t,teacherCard tc where t.tcid= tc.tcid and t.tid = (select c.tid from course c where cname = 'sql') ;

结果如下:

a.在from子查询中,只有一张表;

b.在from子查询中,如果table1 union table2,则table1就是derived表;

explain select cr.cname from ( select * from course where tid = 1 union select * from course where tid = 2 ) cr ;

结果如下:

type关键字可以很好的说明,你写的SQL语句好不好。system、const是我们达不到的理想状况,实际上只能优化到index --> range --> ref这个级别,也可以看到ALL是最差的一个级别。

对type进行优化的前提,是你得创建索引。

源表只有一条数据(实际中,基本不可能);

衍生表只有一条数据的主查询(偶尔可以达到)。

仅仅能查到一条数据的SQL ,仅针对Primary key主键索引或unique索引类型有效。

explain select tid from test01 where tid =1 ;

结果如下:

删除以前的主键索引后,此时我们添加一个其他的普通索引:

create index test01_index on test01(tid) ; # 再次查看执行计划 explain select tid from test01 where tid =1 ;

结果如下:

可以发现:当tid字段去掉主键索引,换为普通索引后,优化级别就不是const了。

唯一性索引,对于每个索引键的查询,返回匹配唯一行数据(有且只有1个,不能多 、不能0),并且查询结果和表中数据条数必须一致。

此种情况常见于唯一索引和主键索引。

delete from teacher where tcid >= 4; alter table teacherCard add constraint pk_tcid primary key(tcid); alter table teacher add constraint uk_tcid unique index(tcid) ; explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid ;

结果如下:

总结:以上SQL,用到的索引是t.tcid,即teacher表中的tcid字段;如果teacher表的数据个数和连接查询的数据个数一致(都是3条数据),则有可能满足eq_ref级别;否则无法满足。条件很苛刻,很难达到。

非唯一性索引,对于每个索引键的查询,返回匹配的所有行(可以0,可以1,可以多)

准备数据:

创建索引,并查看执行计划:

# 添加索引 alter table teacher add index index_name (tname) ; # 查看执行计划 explain select * from teacher where tname = 'tz';

结果如下:

检索指定范围的行 ,where后面是一个范围查询(between, >, <, >=, in)

in有时候会失效,从而转为无索引时候的ALL

# 添加索引 alter table teacher add index tid_index (tid) ; # 查看执行计划:以下写了一种等价SQL写法,查看执行计划 explain select t.* from teacher t where t.tid in (1,2) ; explain select t.* from teacher t where t.tid <3 ;

结果如下:

查询全部索引中的数据(扫描整个索引)

查询全部源表中的数据(暴力扫描全表)

注意:cid是索引字段,因此查询索引字段,只需要扫描索引表即可。但是tid不是索引字段,查询非索引字段,需要暴力扫描整个源表,会消耗更多的资源。

possible_keys可能用到的索引。是一种预测,不准。了解一下就好。

key指的是实际使用的索引。

# 先给course表的cname字段,添加一个索引 create index cname_index on course(cname); # 查看执行计划 explain select t.tname ,tc.tcdesc from teacher t,teacherCard tc where t.tcid= tc.tcid and t.tid = (select c.tid from course c where cname = 'sql') ;

结果如下:

有一点需要注意的是:如果possible_key/key是NULL,则说明没用索引。

索引的长度:用于判断复合索引是否被完全使用(a,b,c)。

# 创建表 create table test_kl ( name char(20) not null default '' ); # 添加索引 alter table test_kl add index index_name(name) ; # 查看执行计划 explain select * from test_kl where name ='' ;

结果如下:

结果分析:因为我没有设置服务端的字符集,因此默认的字符集使用的是latin1,对于latin1一个字符代表一个字节,因此这列的key_len的长度是20,表示使用了name这个索引。

# 新增一个字段name1,name1可以为null alter table test_kl add column name1 char(20) ; # 给name1字段,设置为索引字段 alter table test_kl add index index_name1(name1) ; # 查看执行计划 explain select * from test_kl where name1 ='' ;

结果如下:

结果分析:如果索引字段可以为null,则mysql底层会使用1个字节用于标识null。

# 删除原来的索引name和name1 drop index index_name on test_kl ; drop index index_name1 on test_kl ; # 增加一个复合索引 create index name_name1_index on test_kl(name,name1); # 查看执行计划 explain select * from test_kl where name1 = '' ; explain select * from test_kl where name = '' ;

结果如下:

结果分析:对于下面这个执行计划,可以看到我们只使用了复合索引的第一个索引字段name,因此key_len是20,这个很清楚。再看上面这个执行计划,我们虽然仅仅在where后面使用了复合索引字段中的name1字段,但是你要使用复合索引的第2个索引字段,会默认使用了复合索引的第1个索引字段name,由于name1可以是null,因此key_len = 20 + 20 + 1 = 41呀!

# 新增一个字段name2,name2可以为null alter table test_kl add column name2 varchar(20) ; # 给name2字段,设置为索引字段 alter table test_kl add index name2_index(name2) ; # 查看执行计划 explain select * from test_kl where name2 = '' ;

结果如下:

结果分析:key_len = 20 + 1 + 2,这个20 + 1我们知道,这个2又代表什么呢?原来varchar属于可变长度,在mysql底层中,用2个字节标识可变长度。

这里的ref的作用,指明当前表所参照的字段。

注意与type中的ref值区分。在type中,ref只是type类型的一种选项值。

# 给course表的tid字段,添加一个索引 create index tid_index on course(tid); # 查看执行计划 explain select * from course c,teacher t where c.tid = t.tid and t.tname = 'tw';

结果如下:

结果分析:有两个索引,c表的c.tid引用的是t表的tid字段,因此可以看到显示结果为【数据库名.t.tid】,t表的t.name引用的是一个常量"tw",因此可以看到结果显示为const,表示一个常量。

被索引优化查询的数据个数 (实际通过索引而查询到的数据个数)

explain select * from course c,teacher t where c.tid = t.tid and t.tname = 'tz' ;

结果如下:

表示其他的一些说明,也非常有用,通过这个关键字也可以很好的说明,你写的SQL语句到底好不好。

当出现了这个词,表示你当前的SQL性能消耗较大。表示进行了一次额外的排序。常见于order by语句中。

Ⅰ 什么是“额外”的排序?

为了讲清楚这个,我们首先要知道什么是排序。我们为了给某一个字段进行排序的时候,首先你得先查询到这个字段,然后在将这个字段进行排序。

紧接着,我们查看如下两个SQL语句的执行计划。

# 新建一张表,建表同时创建索引 create table test02 ( a1 char(3), a2 char(3), a3 char(3), index idx_a1(a1), index idx_a2(a2), index idx_a3(a3) ); # 查看执行计划 explain select * from test02 where a1 ='' order by a1 ; explain select * from test02 where a1 ='' order by a2 ;

结果如下:

结果分析:对于第一个执行计划,where后面我们先查询了a1字段,然后再利用a1做了依次排序,这个很轻松。但是对于第二个执行计划,where后面我们查询了a1字段,然而利用的却是a2字段进行排序,此时myql底层会进行一次查询,进行“额外”的排序。

总结:对于单索引,如果排序和查找是同一个字段,则不会出现using filesort;如果排序和查找不是同一个字段,则会出现using filesort;

因此where哪些字段,就order by哪些些字段。

不能跨列(官方术语:最佳左前缀),这句话一定要牢记!!!

# 删除test02的索引 drop index idx_a1 on test02; drop index idx_a2 on test02; drop index idx_a3 on test02; # 创建一个复合索引 alter table test02 add index idx_a1_a2_a3 (a1,a2,a3) ; # 查看下面SQL语句的执行计划 explain select *from test02 where a1='' order by a3 ; --using filesort explain select *from test02 where a2='' order by a3 ; --using filesort explain select *from test02 where a1='' order by a2 ;

结果如下:

结果分析:复合索引的顺序是(a1,a2,a3),可以看到a1在最左边,因此a1就叫做最佳左前缀,

如果要使用后面的索引字段,必须先使用到这个a1字段。

对于explain1,where后面我们使用a1字段,但是后面的排序使用了a3,直接跳过了a2,属于跨列;对于explain2,where后面我们使用了a2字段,直接跳过了a1字段,也属于跨列;对于explain3,where后面我们使用a1字段,后面使用的是a2字段,因此没有出现【using filesort】。

当出现了这个词,也表示你当前的SQL性能消耗较大。这是由于当前SQL用到了临时表,一般出现在group by中。

explain select a1 from test02 where a1 in ('1','2','3') group by a1 ; explain select a1 from test02 where a1 in ('1','2','3') group by a2 ; --using temporary

结果如下:

结果分析:当你查询哪个字段,就按照那个字段分组,否则就会出现using temporary。

---------------------------------------------------------------------------------------------------------------

针对using temporary,我们在看一个例子:

using temporary表示需要额外再使用一张表,一般出现在group by语句中。虽然已经有表了,但是不适用,必须再来一张表。

再次来看mysql的编写过程和解析过程。

Ⅰ 编写过程

select dinstinct ..from ..join ..on ..where ..group by ..having ..order by ..limit ..

Ⅱ 解析过程

from .. on.. join ..where ..group by ..having ..select dinstinct ..order by ..limit ..

很显然,where后是group by,然后才是select。基于此,我们再查看如下两个SQL语句的执行计划。

explain select * from test03 where a2=2 and a4=4 group by a2,a4; explain select * from test03 where a2=2 and a4=4 group by a3;

分析如下:对于第一个执行计划,where后面是a2和a4,接着我们按照a2和a4分组,很明显这两张表已经有了,直接在a2和a4上分组就行了。但是对于第二个执行计划,where后面是a2和a4,接着我们却按照a3分组,很明显我们没有a3这张表,因此有需要再来一张临时表a3,因此就会出现using temporary。

当你看到这个关键词,恭喜你,表示你的SQL性能提升了。

using index称之为

索引覆盖。

当出现了using index,就表示不用读取源表,而只利用索引获取数据,不需要回源表查询。

只要使用到的列,全部出现在索引中,就是索引覆盖。

# 删除test02中的复合索引idx_a1_a2_a3 drop index idx_a1_a2_a3 on test02; # 重新创建一个复合索引idx_a1_a2 create index idx_a1_a2 on test02(a1,a2); # 查看执行计划 explain select a1,a3 from test02 where a1='' or a3= '' ; explain select a1,a2 from test02 where a1='' and a2= '' ;

结果如下:

结果分析:这里我们创建的是a1和a2的复合索引,对于第一个执行计划,我们却出现了a3,该字段并没有创建索引,因此没有出现using index,而是using where,表示我们需要回表查询。对于第二个执行计划,属于完全的索引覆盖,因此出现了using index。

----------------------------------------------------------------------------------------------------------------

针对using index,我们在查看一个案例:

explain select a1,a2 from test02 where a1='' or a2= '' ; explain select a1,a2 from test02 ;

结果如下:

如果用到了索引覆盖(using index时),会对possible_keys和key造成影响:

a.如果没有where,则索引只出现在key中;

b.如果有where,则索引 出现在key和possible_keys中。

表示需要【回表查询】,表示既在索引中进行了查询,又回到了源表进行了查询。

# 删除test02中的复合索引idx_a1_a2 drop index idx_a1_a2 on test02; # 将a1字段,新增为一个索引 create index a1_index on test02(a1); # 查看执行计划 explain select a1,a3 from test02 where a1="" and a3="" ;

结果如下:

结果分析:我们使用了索引a1,表示我们使用了索引进行查询。但是又对于a3字段,我们并没有使用索引,因此对于a3字段,需要回源表查询,这个时候出现了using where。

当where子句永远为False的时候,会出现impossible where。

# 查看执行计划 explain select a1 from test02 where a1="a" and a1="b" ;

结果如下:

# 创建新表 create table test03 ( a1 int(4) not null, a2 int(4) not null, a3 int(4) not null, a4 int(4) not null ); # 创建一个复合索引 create index a1_a2_a3_test03 on test03(a1,a2,a3); # 查看执行计划 explain select a3 from test03 where a1=1 and a2=2 and a3=3;

结果如下:

【推荐写法】:复合索引顺序和使用顺序一致。

【不推荐写法】:复合索引顺序和使用顺序不一致。

# 查看执行计划 explain select a3 from test03 where a3=1 and a2=2 and a1=3;

结果如下:

结果分析:虽然结果和上述结果一致,但是不推荐这样写。但是这样写怎么又没有问题呢?这是由于SQL优化器的功劳,它帮我们调整了顺序。

最后再补充一点:对于复合索引,不要跨列使用。

# 查看执行计划 explain select a3 from test03 where a1=1 and a3=2 group by a3;

结果如下:

结果分析:a1_a2_a3是一个复合索引,我们使用a1索引后,直接跨列使用了a3,直接跳过索引a2,因此索引a3失效了。当再使用a3进行分组的时候,就会出现using where。

# 创建新表 create table book ( bid int(4) primary key, name varchar(20) not null, authorid int(4) not null, publicid int(4) not null, typeid int(4) not null ); # 插入数据 insert into book values(1,'tjava',1,1,2) ; insert into book values(2,'tc',2,1,2) ; insert into book values(3,'wx',3,2,1) ; insert into book values(4,'math',4,2,3) ;

结果如下:

案例:查询authorid=1且typeid为2或3的bid,并根据typeid降序排列。

explain select bid from book where typeid in(2,3) and authorid=1 order by typeid desc ;

结果如下:

这是没有进行任何优化的SQL,可以看到typ为ALL类型,extra为using filesort,可以想象这个SQL有多恐怖。

优化:添加索引的时候,要根据MySQL解析顺序添加索引,又回到了MySQL的解析顺序,下面我们再来看看MySQL的解析顺序。

from .. on.. join ..where ..group by ..having ..select dinstinct ..order by ..limit ..

# 添加索引 create index typeid_authorid_bid on book(typeid,authorid,bid); # 再次查看执行计划 explain select bid from book where typeid in(2,3) and authorid=1 order by typeid desc ;

结果如下:

结果分析:结果并不是和我们想象的一样,还是出现了using where,查看索引长度key_len=8,表示我们只使用了2个索引,有一个索引失效了。

将in字段放在最后面。

需要注意一点:每次创建新的索引的时候,最好是删除以前的废弃索引,否则有时候会产生干扰(索引之间)。

# 删除以前的索引 drop index typeid_authorid_bid on book; # 再次创建索引 create index authorid_typeid_bid on book(authorid,typeid,bid); # 再次查看执行计划 explain select bid from book where authorid=1 and typeid in(2,3) order by typeid desc ;

结果如下:

结果分析:这里虽然没有变化,但是这是一种优化思路。

总结如下:

a.最佳做前缀,保持索引的定义和使用的顺序一致性。

b.索引需要逐步优化(每次创建新索引,根据情况需要删除以前的废弃索引)。

c.将含in的范围查询,放到where条件的最后,防止失效。

本例中同时出现了Using where(需要回原表); Using index(不需要回原表):原因,where authorid=1 and typeid in(2,3)中authorid在索引(authorid,typeid,bid)中,因此不需要回原表(直接在索引表中能查到);而typeid虽然也在索引(authorid,typeid,bid)中,但是含in的范围查询已经使该typeid索引失效,因此相当于没有typeid这个索引,所以需要回原表(using where);

下面这个例子,没有了in,则不会出现using where:

explain select bid from book where authorid=1 and typeid =3 order by typeid desc ;

结果如下:

# 创建teacher2新表 create table teacher2 ( tid int(4) primary key, cid int(4) not null ); # 插入数据 insert into teacher2 values(1,2); insert into teacher2 values(2,1); insert into teacher2 values(3,3); # 创建course2新表 create table course2 ( cid int(4) , cname varchar(20) ); # 插入数据 insert into course2 values(1,'java'); insert into course2 values(2,'python'); insert into course2 values(3,'kotlin');

结果如下:

案例:使用一个左连接,查找教java课程的所有信息。

explain select * from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';

结果如下:

对于两张表,索引往哪里加?答:对于表连接,小表驱动大表。索引建立在经常使用的字段上。

为什么小表驱动大表好一些呢?

小表:10 大表:300 # 小表驱动大表 select ...where 小表.x10=大表.x300 ; for(int i=0;i<小表.length10;i++) { for(int j=0;j<大表.length300;j++) { ... } } # 大表驱动小表 select ...where 大表.x300=小表.x10 ; for(int i=0;i<大表.length300;i++) { for(int j=0;j<小表.length10;j++) { ... } }

分析:以上2个FOR循环,最终都会循环3000次;但是对于双层循环来说:一般建议,将数据小的循环,放外层。数据大的循环,放内层。

不用管这是为什么,这是编程语言的一个原则,对于双重循环,外层循环少,内存循环大,程序的性能越高。

结论:当编写【…on t.cid=c.cid】时,将数据量小的表放左边(假设此时t表数据量小,c表数据量大。)

我们已经知道了,对于两表连接,需要利用小表驱动大表。例如【…on t.cid=c.cid】,t如果是小表(10条),c如果是大表(300条),那么t每循环1次,就需要循环300次,即t表的t.cid字段属于经常使用的字段,因此需要给cid字段添加索引。

更深入的说明:一般情况下,左连接给左表加索引。右连接给右表加索引。其他表需不需要加索引,我们逐步尝试。

# 给左表的字段加索引 create index cid_teacher2 on teacher2(cid); # 查看执行计划 explain select * from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';

结果如下:

当然你可以下去接着优化,给cname添加一个索引。

索引优化是一个逐步的过程,需要一点点尝试。

# 给cname的字段加索引 create index cname_course2 on course2(cname); # 查看执行计划 explain select t.cid,c.cname from teacher2 t left outer join course2 c on t.cid=c.cid where c.cname='java';

结果如下:

最后补充一个:Using join buffer是extra中的一个选项,表示Mysql引擎使用了连接缓存,即MySQL底层动了你的SQL,你写的太差了。

大于等于2张表,优化原则一样;

小表驱动大表 ;

索引建立在经常查询的字段上;

a.复合索引,不要跨列或无序使用(最佳左前缀);

b.复合索引,尽量使用全索引匹配,也就是说,你建立几个索引,就使用几个索引;

explain select * from book where authorid = 1 and typeid = 2; explain select * from book where authorid*2 = 1 and typeid = 2 ;

结果如下:

# 针对不是复合索引的情况 explain select * from book where authorid != 1 and typeid =2 ; explain select * from book where authorid != 1 and typeid !=2 ;

结果如下:

再观看下面这个案例:

# 删除单独的索引 drop index authorid_index on book; drop index typeid_index on book; # 创建一个复合索引 alter table book add index idx_book_at (authorid,typeid); # 查看执行计划 explain select * from book where authorid > 1 and typeid = 2 ; explain select * from book where authorid = 1 and typeid > 2 ;

结果如下:

结论:复合索引中如果有【>】,则自身和右侧索引全部失效。

在看看复合索引中有【<】的情况:

我们学习索引优化 ,是一个大部分情况适用的结论,但由于SQL优化器等原因 该结论不是100%正确。一般而言, 范围查询(> < in)之后的索引失效。

# 删除复合索引 drop index authorid_typeid_bid on book; # 为authorid和typeid,分别创建索引 create index authorid_index on book(authorid); create index typeid_index on book(typeid); # 查看执行计划 explain select * from book where authorid = 1 and typeid =2 ;

结果如下:

结果分析:我们创建了两个索引,但是实际上只使用了一个索引。因为对于两个单独的索引,程序觉得只用一个索引就够了,不需要使用两个。

当我们创建一个复合索引,再次执行上面的SQL:

# 查看执行计划 explain select * from book where authorid = 1 and typeid =2 ;

结果如下:

explain select * from teacher where tname like "%x%" ; explain select * from teacher where tname like 'x%'; explain select tname from teacher where tname like '%x%';

结果如下:

超级重要的SQL优化问题(下)

结论如下:like尽量不要使用类似"%x%"情况,但是可以使用"x%"情况。如果非使用 "%x%"情况,需要使用索引覆盖。

explain select * from teacher where tname = 'abc' ; explain select * from teacher where tname = 123 ;

结果如下:

explain select * from teacher where tname ='' and tcid >1 ; explain select * from teacher where tname ='' or tcid >1 ;

结果如下:

注意:or很猛,会让自身索引和左右两侧的索引都失效。

如果主查询的数据集大,则使用i关键字,效率高。

如果子查询的数据集大,则使用exist关键字,效率高。

select ..from table where exist (子查询) ; select ..from table where 字段 in (子查询) ;

IO就是访问硬盘文件的次数。

using filesort 有两种算法:双路排序、单路排序(根据IO的次数)

MySQL4.1之前默认使用双路排序;双路:扫描2次磁盘(1:从磁盘读取排序字段

,对排序字段进行排序(在buffer中进行的排序)2:扫描其他字段)

MySQL4.1之后默认使用单路排序:只读取一次(全部字段),在buffer中进行排序。但种单路排序会有一定的隐患(不一定真的是“单路/1次IO”,有可能多次IO)。原因:如果数据量特别大,则无法将所有字段的数据一次性读取完毕,因此会进行“分片读取、多次读取”。

注意:单路排序 比双路排序 会占用更多的buffer。

单路排序在使用时,如果数据大,可以考虑调大buffer的容量大小:

# 不一定真的是“单路/1次IO”,有可能多次IO set max_length_for_sort_data = 1024

如果max_length_for_sort_data值太低,则mysql会自动从 单路->双路(太低:需要排序的列的总大小超过了max_length_for_sort_data定义的字节数)

a.选择使用单路、双路 ;调整buffer的容量大小;

b.避免使用select * …(select后面写所有字段,也比写*效率高)

c.复合索引,不要跨列使用 ,避免using filesort

d.保证全部的排序字段,排序的一致性(都是升序或降序)

MySQL SQL 数据库

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:从GaussDB(for MySQL)看全自研数据库的正确打开方式
下一篇:解密华为数据仓库产品GaussDB(DWS)
相关文章