进阶功法:SQL 优化指南

SQL 优化指南

1. 插入数据优化

1.1 批量插入数据

在插入数据时,可以一次插入多条数据以进行 SQL 优化。通常建议一次插入 500 到 1000 条数据。

INSERT INTO tb_test VALUES (1, 'TOM'), (2, 'JERRY'), ...;

1.2 手动提交事务

SQL 在每条语句后都进行提交会影响整体性能。可以通过手动提交事务来减轻负担。

START TRANSACTION;

INSERT INTO tb_test VALUES (1, 'TOM'), (2, 'JERRY');
INSERT INTO tb_test VALUES (3, 'TaM'), (4, 'JyRRY');
INSERT INTO tb_test VALUES (5, 'TeM'), (6, 'JiRRY');

COMMIT;

1.3 主键顺序插入

主键的顺序插入会减轻 SQL 排序操作,直接插入加快速度。

示例:

  • 主键插入:1, 2, 3, 6, 9, 12, 40, 60…

1.4 大批量插入数据

如果一次性插入超大量数据,INSERT 语句的插入性能会很低。可以使用 LOAD DATA INFILE 方法插入数据。

步骤:
  1. 客户端连接服务端时,加上参数 --local-infile
    mysql --local-infile -u root -p
    
  2. 设置全局参数 local_infile 为 1,开启从本地加载文件导入数据的开关。
    SET GLOBAL local_infile = 1;
    
  3. 执行 LOAD DATA INFILE 指令将准备好的数据加载到表结构中。
    LOAD DATA LOCAL INFILE '/root/sql1.log' INTO TABLE tb_user FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';
    

2. 主键优化

在 InnoDB 存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表。

主键设计原则

  1. 降低主键长度:满足业务需求的情况下,尽量降低主键的长度。
  2. 顺序插入:插入数据时,尽量选择顺序插入,使用 AUTO_INCREMENT 自增主键。
  3. 避免使用 UUID 或自然主键:如身份证号等。
  4. 避免修改主键:业务操作时,避免对主键的修改。

拓展知识

  • 页合并和页分裂

3. ORDER BY 优化

ORDER BY 排序具有两种排序方式:

3.1 Using filesort

通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sortbuffer 中完成排序操作。所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。

3.2 Using index

通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。

示例
-- 使用 Using filesort
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY name;

-- 使用 Using index
CREATE INDEX idx_user_age_phone_aa ON tb_user(age, phone);

EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age, phone;
EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age DESC, phone DESC;

-- 创建混合排序索引
CREATE INDEX idx_user_age_phone_aa ON tb_user(age ASC, phone DESC);

EXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age ASC, phone DESC;

3.3 ORDER BY 优化原则

  1. 建立合适的索引:根据排序字段建立合适的索引,多字段排序时,遵循最左前缀法则。
  2. 覆盖索引:尽量使用覆盖索引。
  3. 多字段排序:一个升序一个降序时,注意联合索引在创建时的规则(ASC/DESC)。
  4. 增大排序缓冲区:如果不可避免地出现 FileSort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认 256k)。

4. GROUP BY 优化

GROUP BY 优化同样借助索引进行优化。

示例
-- 效率较低
EXPLAIN SELECT profession, COUNT(*) FROM tb_user GROUP BY profession;

-- 建立索引后,效率提升
CREATE INDEX idx_user_pro_age_sta ON tb_user(profession, age, status);

EXPLAIN SELECT profession, COUNT(*) FROM tb_user GROUP BY profession;

4.1 GROUP BY 优化原则

  1. 通过索引提高效率:在分组操作时,可以通过索引来提高效率。
  2. 最左前缀法则:分组操作时,索引的使用也是满足最左前缀法则的。

5. LIMIT 优化

LIMIT 用于分页操作,当数据量较大时,需要进行优化。

示例
# 当我们希望获得第900000个数据后的十个数据,就需要完全获得前9000000个数据才可以,这会损耗许多时间

# 优化思路:
# 我们通过select只获得第9000000个后的十个数据的id
# 然后通过id对比来获得整行数据:

EXPLAIN SELECT * FROM tb_sku t, (SELECT id FROM tb_sku ORDER BY id LIMIT 900000, 10) a WHERE t.id = a.id;

6. COUNT 优化

不同存储引擎处理 COUNT 的方式不同:

6.1 MyISAM

直接把表的总行数存储在磁盘中,运行 COUNT(*) 时直接输出。

6.2 InnoDB

需要一行一行读取数据,进行累加。

6.3 优化思路

在添加数据和删除数据时,同时存储其数据数量。

6.4 COUNT 的四种常见情况

COUNT 用法含义
COUNT(主键)InnoDB 引擎会遍历整张表,把每一行的主键值都取出来,返回给服务层。服务层按行进行累加(主键不可能为 null)。
COUNT(字段)没有 NOT NULL 约束:InnoDB 引擎会遍历整张表,把每一行的字段值都取出来,返回给服务层,服务层判断是否为 null,不为 null 计数累加。
NOT NULL 约束:InnoDB 引擎会遍历整张表,把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
COUNT(1)InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1”进去,直接按行进行累加。
COUNT(*)InnoDB 引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。

6.5 注意事项

  • COUNT(1)COUNT(*) 速度基本相近,均为最快速度。
  • 按照效率排序:COUNT(字段) < COUNT(主键 id) < COUNT(1) ≈ COUNT(*),所以尽量使用 COUNT(*)

7. UPDATE 优化

InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。

示例
-- 采用行锁
UPDATE course SET name = 'javaEE' WHERE id = 1;

-- 采用表锁
UPDATE course SET name = 'SpringBoot' WHERE name = 'PHP';

7.1 UPDATE 优化原则

  1. 使用索引:更新操作尽量采用索引进行改变,这样锁就会变成行锁,只控制这一行数据。
  2. 避免表锁:如果更新操作没有使用索引,那么会采用表锁,导致整个表的数据都无法改变,影响其他人同步修改该表。

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

在这里插入图片描述

(转)SQL 优化原则 一、问题的提出  在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用系统提交实际应用后,随着数据库中数据的增加,系统的响应速度就成为目前系统需要解决的最主要的问题之一。系统优化中一个很重要的方面就是SQL语句的优化。对于海量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍,可见对于一个系统不是简单地能实现其功能就可,而是要写出高质量的SQL语句,提高系统的可用性。   在多数情况下,Oracle使用索引来更快地遍历表,优化器主要根据定义的索引来提高性能。但是,如果在SQL语句的where子句中写的SQL代码不合理,就会造成优化器删去索引而使用全表扫描,一般就这种SQL语句就是所谓的劣质SQL语句。在编写SQL语句时我们应清楚优化器根据何种原则来删除索引,这有助于写出高性能的SQL语句。   二、SQL语句编写注意问题   下面就某些SQL语句的where子句编写中需要注意的问题作详细介绍。在这些where子句中,即使某些列存在索引,但是由于编写了劣质的SQL,系统在运行该SQL语句时也不能使用该索引,而同样使用全表扫描,这就造成了响应速度的极大降低。   1. IS NULL 与 IS NOT NULL   不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。   任何在where子句中使用is null或is not null的语句优化器是不允许使用索引的。   2. 联接列   对于有联接的列,即使最后的联接值为一个静态值,优化器是不会使用索引的。我们一起来看一个例子,假定有一个职工表(employee),对于一个职工的姓和名分成两列存放(FIRST_NAME和LAST_NAME),现在要查询一个叫比尔.克林顿(Bill Cliton)的职工。   下面是一个采用联接查询的SQL语句, select * from employss where first_name||''||last_name ='Beill Cliton'; 上面这条语句完全可以查询出是否有Bill Cliton这个员工,但是这里需要注意,系统优化器对基于last_name创建的索引没有使用。   当采用下面这种SQL语句的编写,Oracle系统就可以采用基于last_name创建的索引。 *** where first_name ='Beill' and last_name ='Cliton'; . 带通配符(%)的like语句   同样以上面的例子来看这种情况。目前的需求是这样的,要求在职工表中查询名字中包含cliton的人。可以采用如下的查询SQL语句: select * from employee where last_name like '%cliton%'; 这里由于通配符(%)在搜寻词首出现,所以Oracle系统不使用last_name的索引。在很多情况下可能无法避免这种情况,但是一定要心中有底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其他位置时,优化器就能利用索引。在下面的查询中索引得到了使用: select * from employee where last_name like 'c%'; 4. Order by语句   ORDER BY语句决定了Oracle如何将返回的查询结果排序。Order by语句对要排序的列没有什么特别的限制,也可以将函数加入列中(象联接或者附加等)。任何在Order by语句的非索引项或者有计算表达式都将降低查询速度。   仔细检查order by语句以找出非索引项或者表达式,它们会降低性能。解决这个问题的办法就是重写order by语句以使用索引,也可以为所使用的列建立另外一个索引,同时应绝对避免在order by子句中使用表达式。 5. NOT   我们在查询时经常在where子句使用一些逻辑表达式,如大于、小于、等于以及不等于等等,也可以使用and(与)、or(或)以及not(非)。NOT可用来对任何逻辑运算符号取反。下面是一个NOT子句的例子: ... where not (status ='VALID') 如果要使用NOT,则应在取反的短语前面加上括号,并在短语前面加上NOT运算符。NOT运算符包含在另外一个逻辑运算符中,这就是不等于()运算符。换句话说,即使不在查询where子句中显式地加入NOT词,NOT仍在运算符中,见下例: ... where status 'INVALID'; 对这个查询,可以改写为不使用NOT: select * from employee where salary3000; 虽然这两种查询的结果一样,但是第二种查询方案会比第一种查询方案更快些。第二种查询允许Oracle对salary列使用索引,而第一种查询则不能使用索引。 虽然这两种查询的结果一样,但是第二种查询方案会比第一种查询方案更快些。第二种查询允许Oracle对salary列使用索引,而第一种查询则不能使用索引。 =============================================================================================== 我们要做到不但会写SQL,还要做到写出性能优良的SQL,以下为笔者学习、摘录、并汇总部分资料与大家分享! (1) 选择最有效率的表名顺序(只在基于规则的优化器中有效): ORACLE 的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指那个被其他表所引用的表. (2) WHERE子句中的连接顺序.: ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾. (3) SELECT子句中避免使用 ‘ * ‘: ORACLE在解析的过程中, 会将'*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间 (4) 减少访问数据库的次数: ORACLE在内部执行了许多工作: 解析SQL语句, 估算索引的利用率, 绑定变量 , 读数据块等; (5) 在SQL*Plus , SQL*Forms和Pro*C中重新设置ARRAYSIZE参数, 可以增加每次数据库访问的检索数据量 ,建议值为200 (6) 使用DECODE函数来减少处理时间: 使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表. (7) 整合简单,无关联的数据库访问: 如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系) (8) 删除重复记录: 最高效的删除重复记录方法 ( 因为使用了ROWID)例子: DELETE FROM EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID) FROM EMP X WHERE X.EMP_NO = E.EMP_NO); (9) 用TRUNCATE替代DELETE: 当删除表中的记录时,在通常情况下, 回滚段(rollback segments ) 用来存放可以被恢复的信息. 如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况) 而当运用TRUNCATE时, 回滚段不再存放任何可被恢复的信息.当命令运行后,数据不能被恢复.因此很少的资源被调用,执行时间也会很短. (译者按: TRUNCATE只在删除全表适用,TRUNCATE是DDL不是DML) (10) 尽量多使用COMMIT: 只要有可能,在程序中尽量多使用COMMIT, 这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少: COMMIT所释放的资源: a. 回滚段上用于恢复数据的信息. b. 被程序语句获得的锁 c. redo log buffer 中的空间 d. ORACLE为管理上述3种资源中的内部花费
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休居士

感谢您的支持,我会继续努!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值