(二一)SQL优化

insert优化

批量插入

insert into tb_name values
    (col_1, ..., col_n),
    ...;

手动提交事务

start transaction;
insert into tb_name values ...;
commit;

主键顺序插入(主键优化)

主键顺序插入的性能高于乱序插入(会发生页分裂)。

数据组织方式

在lnnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table lOT)。

页分裂

页可以为空,也可以填充一半,也可以填充100%。每个页包含了2~N行数据(如果一行数据多大,会行溢出),根据主键排列。

页合并

当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并它的空间变得允许被其他记录声明使用。

当页中删除的记录达到 MERGE_THRESHOLD(默认为页的50%),nnoDB会开始寻找最靠的页前或后)看看是否可以将两个页合并以优化空间使用。

主键设计原则

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

大批量插入数据:load

如果一次性需要导入大批量数据,用insert插入性能较低,可以采用load插入。

# 客户端连接服务器时,加上参数 --local-infile
mysql --local-infile -u root -p

set global local_infile = 1;

load data local infile '文件路径' 
into table tb_user
fields terminated by ','
lines terminated by '\n';

创建一个数据库,并建立需要导入数据的表结构。

create database test;
use test;
CREATE TABLE tb_user(
	id INT NOT NULL AUTO_INCREMENT
    , username VARCHAR(50) NOT NULL
    , password VARCHAR(50) NOT NULL
    , name VARCHAR(20) NOT NULL
    , birthday DATE DEFAULT NULL
    , sex CHAR(2) DEFAULT NULL
	, PRIMARY KEY (id)
	, UNIQUE KEY unique_user_username(username)
) ENGINE = INNODB DEFAULT CHARSET = utf8;

准备sql脚本数据 tb_user.sql(我一般把数据文件放在C:\ProgramData\MySQL目录下,不容易出错,谨慎尝试数据量巨大的导入,会导致磁盘爆满,我后面没有办法重装系统才好)。(csv文件同理)

脚本数据的结构

导入sql脚本(注意路径中是“/”):

-- sql脚本
load data infile 'C:/ProgramData/MySQL/tb_user.sql'
into table tb_user
fields terminated by ','
lines terminated by '\n';


-- csv文件
load data infile 'C:/ProgramData/MySQL/tb_user.csv'
into table tb_user
fields terminated by ','
lines terminated by '\r\n';

如果出现 ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement ,找到my.ini文件(一般在C:\ProgramData\MySQL\MySQL Server 8.0 目录下)打开,在文末添加 secure_file_priv='' 保存,然后停止mysql再启动mysql((二)MySQL的安装、启动/停止/连接、卸载

order by 优化

  1. 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
  2. 尽量使用覆盖索引。
  3. 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。
  4. 如果不可避免的出现filesort,大数据排序时,可以适当增大排序缓冲区大小 sort buffer size(默认256k)。
  1. Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sotbuffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。(性能较低)
  2. Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率高。
-- 根据country表中的name, gnp进行降序一个升序,一个降序(表中已创建idx_country_name_gnp索引)
explain select code, name, gnp from country order by name asc, gnp desc;

这个时候会出现 using index和using filesort,因为创建索引时,默认为asc。

-- 创建索引 
create index idx_country_name_gnp_ad on country(name asc, gnp desc);

explain select code, name, gnp from country order by name asc, gnp desc;

此时就只会出现using index,性能得到提升。

group by 优化 

use test;
explain select continent, count(*) from country group by continent;

在country表只有一个主键索引时,直接对continent进行group by,会出现using temporary,此时性能较低。

create index idx_country_continent_region on country(continent, region);
explain select continent, count(*) from country group by continent;

创建idx_country_continent_region索引后,对continent进行group by,不出现using temporary;

explain select region, count(*) from country group by region;

直接对region进行group by,会出现using temporary,因为不满足最左前缀法则;

explain select continent, region, count(*) from country group by continent, region;

同时对continent, region进行group by,不出现using temporary;

explain select region, count(*) from country where continent = 'europe' group by region;

加入continent过滤后,不出现using temporary,满足最左前缀法则;

limit 优化

一个常见又非常头疼的问题就是 limit 2000000,10 ,此时需要MySOL排序前2000010 记录,仅仅返回2000000 -2000010的记录,其他记录丢弃,查询排序的代价非常大。

优化思路:一般分页查询时,通过创建 覆盖索引 能够比较好地提高性能,可以通过覆盖索引+子查询形式进行优化。

explain select * from tb sku t , (select id from tb sku order by id limit 2000000,10) a where t.id = a.id:

count优化

  • MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高
  • InnoDB 引擎执行 count(*)的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。

优化思路:自己计数

count的几种用法

count是一个聚合函数,对于返回的结果集,一行行地判断,如果ount 函数的参数不是 NULL,累计值就加1,否则不加,最后返回累计值。

用法:count (*)、count (主键)、count (字段)、count (1)

count(主键):InnoDB引擎会遍历整张表,把每一行的 主键id 值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为null).

count (字段):没有not nul 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为nul,不为nul,计数累加有not null 约束:lnnoDB 引警会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。

count (1):lnnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1”进去,直接按行进行累加。

count (*):InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。

按照效率排序的话,count(字段)< count(主键 id)< count(1)= count(*),所以尽量使用 count(*)。

update 优化

  • 在使用update进行更新数据时,要避免以没有索引的列为条件,这样在执行事务的时候,行级锁会升级为表级锁,会锁住整张表,这样执行的性能就会降低
  • 此外,如果索引失效,行锁也会升级为表锁。

Innodb特点:事务,外键,行级锁。Innodb是对索引加的行锁,不是对记录加的。

首先在A窗口中开启一个事务,先不提交

--------------------------窗口A-------------------------------

use world;

begin;

select * from city;

-- 执行update语句, 此时id=1的行锁定
update city set name = 'new_name_1' where id = 1; 

再在窗口B同时开启一个事务:

--------------------------窗口B-------------------------------

use world;

begin;

select * from city;

-- 执行update语句,id=4可以执行成功
update city set name = 'new_name_2' where id = 4;

分别提交:

commit;

现象分析:窗口A和B都能执行成功的原因是,id字段有索引,上锁的时候上的是行级锁。且update语句中的条件不同(id=1和id=4),互不影响,故能够执行成功。

如果update语句中的条件字段没有索引呢?即使条件不同,也有影响,不能同时执行,因为上的是表锁,锁定了整张表。只有窗口A提交之后,窗口B才能执行。

--------------------------窗口A-------------------------------

use world;

begin;

select * from city;

-- 执行update语句, name没有索引,则上的是表锁
update city set name = 'new_name_1' where name = 'Kabul'; 
--------------------------窗口B-------------------------------

use world;

begin;

select * from city;

-- 执行update语句, 不能执行成功,因为表被锁定了
update city set name = 'new_name_2' where name = 'Mazar-e-Sharif';

不总结=白学 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值