都有哪些维度可以进行数据库调优?简言之:
虽然SQL查询优化的技术有很多,但是大方向上完全可以分成物理查询优化和逻辑查询优化两大块。
MysQL中提高性能的一个最有效的方式是对数据表设计合理的索引。索引提供了高效访问数据的方法,并且加快查询的速度,因此索引对查询的速度有着至关重要的影响。
大多数情况下都(默认)采用B+树来构建索引。只是空间列类型的索引使用R-树,并且MEMORY表还支持hash索引。
其实,用不用索引,最终都是优化器说了算。
优化器是基于什么优化?
基于cost开销(CostBaseOptimizer),它不是基于规则(Rule-Based0ptimizer),也不是基于语义。怎么样开销小就怎么来。
另外,SQL语句是否使用索引,跟数据库版本、数据量、数据选择度都有关系。
CREATE INDEX idx_age ON student(age);
CREATE INDEX idx_age_classid ON student(age,classId);
CREATE INDEX idx_age_classid_name ON student(age,classId,NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30; # 使用 idx_age
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4;# idx_age_classid_name
EXPLAIN SELECT SQL_NO_CACHE age,classId FROM student WHERE age=30 AND classId=4;# idx_age_classid
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME = 'abcd'; # idx_age_classid_name
在MysQL建立联合索引时会遵守最佳左前缀匹配原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。
最佳左前缀法则针对是索引中字段顺序,而不是sql字段顺序,sql字段顺序和索引不一致,优化器会帮忙调整,不影响索引生效。
CREATE INDEX idx_age_classid_name ON student(age,classId,NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE classid=4 AND student.age=30 AND student.name = 'abcd'; # idx_age_classid_name
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age=30 AND student.name = 'abcd'; # idx_age_classid_name 中只用到了 age
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.classid=30 AND student.name = 'abcd'; # 未用到
结论:MysQL可以为多个字段创建索引,一个索引可以包括16个字段。对于多列索引,**过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。**如果查询条件中没有使用这些字段中第1个字段时,多列(或联合)索引不会被使用。
数据页已经满了,再插进来咋办呢?
我们需要把当前页面分裂成两个页面,把本页中的一些记录移动到新创建的这个页中。页面分裂和记录移位意味着什么?
意味着:性能损耗!所以如果我们想尽量避免这样无谓的性能损耗,最好让插入的记录的主键值依次递增,这样就不会发生这样的性能损耗了。
所以我们建议:让主键具有AUTO_INCREMENT,让存储引擎自己为表生成主键,而不是我们手动插入。
CREATE INDEX idx_name ON student(NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';# idx_name
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc'; # 未使用到索引 LEFT(student.name,3)不知道是哪个字段
CREATE INDEX idx_sno ON student(stuno);
EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno+1 = 900001; # 失效
EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno = 900000; # idx_sno
CREATE INDEX idx_name ON student(NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME = 123; # 失效
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME = '123'; # idx_name
CREATE INDEX idx_age_classId_name ON student(age,classId,NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ; # idx_age_classId_name,但是name字段未用到索引,效果等同于下面sql
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.name = 'abc' AND student.classId>20;
要想避免上述情况,可以优化:
CREATE INDEX idx_age_classId_name ON student(age,NAME,classId);
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ;
# 上下 sql 等同
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.name = 'abc' AND student.classId>20;
应用开发中范围查询,例如:金额查询,日期查询往往都是范围查询。应将查询条件放置where语句最后。
CREATE INDEX idx_name ON student(NAME);
# 以下 sql 都失效
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name <> 'abc' ;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name != 'abc' ;
CREATE INDEX idx_age ON student(age);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NULL;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NOT NULL;
CREATE INDEX idx_name ON student(NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME LIKE 'ab%'; # idx_name
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME LIKE '%ab%'; # 失效
CREATE INDEX idx_age ON student(age);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR classid = 100; # 失效
统一使用utf8mb4( 5.5.3版本以上支持)兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。不同的 字符集 进行比较前需要进行 转换 会造成索引失效。
总之,书写SQL语句时,尽量避免造成索引失效的情况。
MySQL从4.1版本开始支持子查询,使用子查询可以进行SELECT语句的嵌套查询,即一个SELECT查询的结果作为另一个SELECT语句的条件。 子查询可以一次性完成很多逻辑上需要多个步骤才能完成的SQL操作 。子查询是 MySQL 的一项重要的功能,可以帮助我们通过一个 SQL **语句实现比较复杂的查询。但是,子查询的执行效率不高。**原因:
**在MySQL中,可以使用连接(JOIN)查询来替代子查询。**连接查询 不需要建立临时表 ,其 速度比子查询要快 ,如果查询中使用索引的话,性能就会更好。
结论:尽量不要使用NOT IN 或者 NOT EXISTS,用LEFT JOIN xxx ON xx WHERE xx IS NULL替代
理解方式一:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了满足查询结果的数据就叫做覆盖索引。
理解方式二:非聚簇复合索引的一种形式,它包括在查询里的SELECT、JOIN和WHERE子句用到的所有列(即建索引的字段正好是覆盖查询条件中所涉及的字段)。
简单说就是, 索引列+主键 包含 SELECT 到 FROM之间查询的列 。
好处:
弊端:
MySQL是支持前缀索引的,也就是说,你可以定义字符串的一部分作为索引。默认地,如果你创建索引的语句不指定前缀长度,那么索引就会包含整个字
符串。
mysql> alter table teacher add index index1(email);
#或
mysql> alter table teacher add index index2(email(6));
**使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。**前面已经讲过区分度,区分度越高越好。因为区分度越高,意味着重复的键值越少。
结论:使用前缀索引就用不上覆盖索引对查询性能的优化了,这也是你在选择是否使用前缀索引时需要考虑的一个因素。
Index Condition Pushdown(ICP)是MySQL 5.6中新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式。ICP可以减少存储引擎访问基表的次数以及MySQL服务器访问存储引擎的次数。
索引下推是把本应该在 server 层进行筛选的条件,下推到存储引擎层来进行筛选判断,这样能有效减少回表。
默认情况下启用索引条件下推。可以通过设置系统变量 optimizer_switch控制:index_condition_pushdown
#关闭索引下推
SET optimizer_switch = 'index_condition_pushdown=off';
#打开索引下推
SET optimizer_switch = 'index_condition_pushdown=on';
当使用索引条件下推时,EXPLAIN语句输出结果中Extra列内容显示为Using index condition
例如:
EXPLAIN SELECT * FROM people
WHERE zipcode='000001'
AND lastname LIKE '%张%'
AND address LIKE '%北京市%';
# 上面如果不使用索引下推,将只会用到zipcode就回表
EXPLAIN SELECT * FROM people
WHERE zipcode='000001'
AND lastname LIKE '张%'
AND firstname LIKE '三%';
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE people \N range zip_last_first zip_last_first 189 \N 1 25.00 Using index condition
问题:
不太理解哪种情况下应该使用EXISTS,哪种情况应该用IN。选择的标准是看能否使用表的索引吗?
回答:
索引是个前提,其实选择与否还是要看表的大小。你可以将选择的标准理解为小表驱动大表。在这种方式下效率是最高的。
问:在MySQL中统计数据表的行数,可以使用三种方式: SELECT COUNT(*)、SELECT COUNT(1)和SELECT COUNT(具体字段),使用这三者之间的查询效率是怎样的?
答:
前提:如果你要统计的是某个字段的非空数据行数,则另当别论,毕竟比较执行效率的前提是结果一样才可以。
环节1: **COUNT(*)和COUNT(1)都是对所有结果进行COUNT,COUNT(*)和COUNT(1)**本质上并没有区别(二者执行时间可能略有差别,不过你还是可以把它俩的执行效率看成是相等的)。如果有WHERE子句,则是对所有符合筛选条件的数据行进行统计;如果没有WHERE子句,则是对数据表的数据行数进行统计。
环节2:如果是MylSAM存储引擎,统计数据表的行数只需要O(1)的复杂度,这是因为每张MyISAM的数据表都有一个meta信息存储了row_count值,而一致性则由表级锁来保证。
如果是InnoDB存储引擎,因为InnoDB支持事务,采用行级锁和MVCC机制,所以无法像MylSAM一样,维护一个row_count变量,因此需要采用扫描全表,是o(n)的复杂度,进行循环+计数的方式来完成统计。
环节3:在InnoDB引擎中,如果采用COUNT(具体字段)来统计数据行数,要尽量采用二级索引。因为主键采用的索引是聚簇索引,聚簇索引包含的信息多,明显会大于二级索引(非聚簇索引)。对于COUNT(*)和COUNT(1)来说,它们不需要查找具体的行,只是统计行数,系统会自动采用占用空间更小的二级索引来进行统计。如果有多个二级索引,会使用key_len小的二级索引进行扫描。当没有二级索引的时候,才会采用主键索引来进行统计。
在表查询中,建议明确字段,不要使用*作为查询的字段列表,推荐使用SELECT <字段列表>查询。
原因:
针对的是会扫描全表的SQL语句,如果你可以确定结果集只有一条,那么加上LIMIT 1的时候,当找到一条结果的时候就不会继续扫描了,这样会加快查询速度。
如果数据表已经对字段建立了唯一索引,那么可以通过索引进行查询,不会全表扫描的话,就不需要加上LIMIT1了。
只要有可能,在程序中尽量多使用COMMIT,这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少。
COMMIT所释放的资源:
https://www.bilibili.com/video/BV1iq4y1u7vj/?spm_id_from=333.337.search-card.all.click&vd_source=25b05e9bd8b4bdac16ca2f47bbeb7990