01 插入数据
【一】insert优化:
① 不要一行一行插入数据,直接批量插入多行数据(但一个insert语句不要插入超过一千条)。
insert into 表名 (字段列表) values (xx,xx...),(xx,xx...)...;
② 在进行插入操作时手动提交事务,避免频繁的开启提交事务。
start transaction;
insert into 表名 (字段列表) values (xx,xx...),(xx,xx...)...;
insert into 表名 (字段列表) values (xx,xx...),(xx,xx...)...;
insert into 表名 (字段列表) values (xx,xx...),(xx,xx...)...;
commit;
③ 按照主键顺序插入,因为按照主键顺序的性能比乱序插入的性能高。
【二】大批量的数据插入:
① 说明:大批量的数据插入,使用insert语句的性能较低,此时得使用load指令进行插入。
通过load指令可以一次性将本地文件中的数据全部加载进数据库表结构中。
② 使用load指定要做的三步操作:
a. 客户端连接服务器时,加上参数 --local-infile
mysql --local-infile -u root -p
b. 查看MySQL从本地加载文件导入数据的开关
select @@local_infile;---》1为开启,0为关闭
b. 设置全局参数local_infile为1,开启MySQL从本地加载文件导入数据的开关
set global local_infile = 1;
c. 建立一个表,等会用于存放大批量数据
d. 上传本地含有大批量数据的文件,到linux服务器中,上传后一些相关查看操作如下图所示:
e. 使用load指令大批量添加数据到数据库表结构中:
load data local infile '含有大批量数据的文件路径' into table 表名 fields terminated by ‘字段分隔符’ lines terminated by '换行符';
记法:记载数据本地文件 哪个文件 给哪个表 场地的分隔符是什么 行的换行符是什么
③ 使用load指令插入数据时,最好也是按照主键顺序插入,因为这样性能高。
02 主键优化
【说明】:逐渐优化主要讨论按照主键顺序插入的性能为什么比乱序插入的高。
【一】InnoDB存储引擎中数据组织方式
InnoDB存储引擎中,表数据都是根据主键顺序组织存放的( 因为InnoDB存储引擎根据索引的存储结构将索引又分为聚集索引和二级索引,聚集索引每个叶子节点下存储的是每一行数据,有主键索引的表,聚集索引由主键索引构成)。
【二】InnoDB的逻辑存储结构
① 页是InnoDB存储引擎磁盘管理的最小单元,一个页的大小为16k
② 一个区的大小为1M,可以包含64个页
【三】往数据库表结构中插入数据的底层原理
① 主键顺序插入:一个页满了之后,再使用下一个页
② 主键乱序插入:
a. 为了保证索引的有序性,在乱序插入时通常会发生 页分裂 现象
b. 页分裂:如下图所示,50只能插入到47的后面,但page1已经满的,此时会将page1一半的数据移动到page3中,
然后将50插入到47之后,并修改page1,2,3的链表走向。
c. 补充:页合并现象:当删除一行记录时,实际上并不会进行物理删除,只是会把它标记成删除状态,一旦被标记成删除状态,表示这块空间允许被其他记录声明使用。当某一页中删除的记录达到 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最近的页(前或后),看看是否可以将两个页合并以优化空间的使用。
【四】主键的设计原则
① 在满足业务需求的情况下,尽量降低主键的长度
(原因:在一张数据库表结构中,主键索引和聚集索引只能有一个,而二级索引可以有很多个,二级索引的叶子节点下存储的就是对应的主键,如果主键索引较长,二级索引较多,会占用大量的磁盘空间,在查询的时候也会耗费大量的磁盘IO)
② 插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键
③ 尽量不要使用UUID(和rand类似,可生成随机数)做主键或者其他的自然主键,例如:身份证号,因为无序就会发生页分裂,而且长度较长,会消耗大量的磁盘IO。
03 order by优化
【一】:MySQL中的排序方式
① Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫FileSort排序。
② Using index:通过通过索引直接返回排序结果,不需要额外排序,性能比Using filesort要高,优化order by时尽量让排序方式为Using index。
③ 补充:在建立索引时,如果不指定字段的排序方式,默认是升序排列
指定语法:create index 索引名 on 表名 (字段名1 asc, 字段名2 desc ...);
解释:create index idex_user_age_pho_ad on tb_user (age asc, phone desc);表示创建一个联合索引,索引按age升序排序,在age相同的情况下按phone降序排序
【二】:判断某条select语句的排序,到底使用的那种排序方式 :
① 一个字段,无索引,filesort
② 一个字段,有索引,覆盖索引,index
③ 一个字段,有索引,非覆盖索引,filesort
④ 多个字段,都无索引,filesort
⑤ 多个字段,都有索引,但并不构成联合索引,filesort
⑥ 多个字段,都有索引,构成联合索引,但不符合最左前缀法则,覆盖索引,index,filesort
⑦ 多个字段,都有索引,构成联合索引,但不符合最左前缀法则,非覆盖索引,filesort
--》因为要select联合索引中没有的字段,不能在直接走联合索引拿数据再去排序了。
⑧ 多个字段,都有索引,构成联合索引,字段都是同升序/降序,覆盖索引,index
⑨ 多个字段,都有索引,构成联合索引,字段存在有升序,降序的,index,filesort
(可以建立新的联合索引,把filesort优化掉,但也必须要满足覆盖索引)
⑩ 多个字段,都有索引,构成联合索引,非覆盖索引,filesort
【关于底层的一些说明】
① 磁盘文件中可以是存储着数据库表中一行一行的数据,磁盘文件中也可以是存放着索引的数据结构和指向数据库表每一行数据的指针,当我们去使用索引查找数据时,实际上最终也是会根据地址找对应的数据,相当于多了一次磁盘io。
② 聚集索引虽然存储着数据库表中的数据,但MySQL是不会主动使用聚集索引去拿每一行的数据的,除非你主动使用了聚集索引,例如:order by id等,拿数据库表中的信息一般是在对应的磁盘文件中进行全表扫描拿到。
③ 如果order by后面的字段没有索引,则会对数据库表结构进行全表扫描,拿到每一行的数据,然后在排序缓冲区中进行相关的排序,此种排序方式属于filesort。
④ 多个字段,都有索引,构成联合索引,字段都是同升序/降序,覆盖索引,为什么排序方式是index?答:由于order by后面排序的字段有联合索引,且字段都是同升序/降序(这种情况与创建索引时字段的顺序是一致或相反的,相反可以反向扫描)且符合覆盖索引 (此时select查找排序后的内容都包含在联合索引中),因此我们可以直接通过走联合索引拿到最终的排序结果,所以排序方式是index。
⑤ 多个字段,都有索引,构成联合索引,字段都是同升序/降序,不满足覆盖索引,为什么排序方式是filesort?答:因为走联合索引只能拿到联合索引中存在字段的排序结果,如果select查询时还要查询联合索引中并不存在的字段,则此时我们需要对数据库表结构进行全表扫描,拿到每一行的数据,然后在排序缓冲区中再进行相关的排序,所以排序的方式是filesort。
⑥ 多个字段,都有索引,构成联合索引,但不符合最左前缀法则,覆盖索引,为什么排序方式是index,filesort?因为虽然我们不能直接使用联合索引去拿到最终的排序结果,但我们可以利用该联合索引拿到每一行的数据,然后再在排序缓冲区中进行排序,毕竟B+树中有现成的数据且字段都是我们想要的,和去磁盘中拿,当然是走联合索引去拿数据更好,所以排序的方式是index,filesort。
⑦ 多个字段,都有索引,构成联合索引,字段存在有升序,降序的,为什么排序方式是index,filesort?同上⑥。
【三】order by 优化:
① 根据order by后面的字段建立合适的索引,多字段排序时,也遵循最左前缀法则
② 查询排序后的结果时,尽量使用覆盖索引,否则又会走Using filesort
③ 多字段,一个升序,一个降序等类似字段排序规则不统一的情况,我们可以在创建相关的联合索引时指定字段的排序方式
④ 如果不可避免出现了filesort那就使用它吧,在大数据排序时,我们可以适当增大排序缓冲区大小sort_buffer_size,因为如果排序缓冲区放不下,会在磁盘文件中进行排序,这样相对在排序缓冲区排序效率较低
a. 查看排序缓冲区的大小:select variables like 'sort_buffer_size';--> buffer缓冲
b. 修改排序缓冲区的大小:set global sort_buffer_size = xxx;
04 group by优化
【一】分析不同情况下,使用的分组方式:
① 根据group by后面的字段进行分组操作时,如果group by后面的字段没有索引则会进行全表扫描拿到表中的数据后在临时表中按照相关字段进行分组处理。
② 如果group by后面的字段有索引 (一个字段/联合索引满足最左前缀法则),覆盖索引,由于索引底层其实相当于分好组了,所以直接走索引就可以拿到分组的结果。
③ 如果group by后面的字段有索引 (联合索引不满足最左前缀法则),覆盖索引,此时会通过走索引拿到数据,然后再将数据拿到临时表中按照相关字段进行分组处理,因此分组的方式为index, Using temporary(使用临时表)
④ 注意:使用where进行条件筛选后,再使用group by对不满足最左前缀法则字段分组,的效果等同于满足最左前缀法则的效果
例如:where pro group by age == group by pro, age
【二】group by优化:
① 给group by后面的字段建立适当的索引,通过走索引直接拿的性能比走全表扫描再在临时表中分组效率要高很多。
② 分组操作时索引的使用也是满足最左前缀法则的。
05 limit优化
【一】现象:对于limit来说,大数据量的情况下进行分页查询,起始索引越大所需时间越长,效率越低。
【二】limit优化:
① 如果主键是连续的可以先使用where过滤掉前面的数据,然后再使用 limit 进行分页查询
例如:select id,name form user where id > 1000000 limit 10;
② 如果主键不是连续的,可以使用覆盖索引+子查询的方式:先对主键进行排序,然后limit,并使用覆盖索引拿到待查询数据的主键;然后利用子查询,查询到待查数据的每一行。
例如:select id from tb_sku order by id limit 500000,10;
select * from tb_sku where id in (select id from tb_sku order by id limit 500000,10);
【三】下图中的说明:由于当前的版本不支持limit存在于in中,解决方法是采用多表查询
06 count优化
【一】count是一个聚合函数,用于求取符合条件且参数不为null的总的记录数的。
【二】count求取符合条件的总的数据量的快慢取决于存储引擎的处理方式:
① MySIAM存储引擎中,它会自动统计表中总的记录数,因此在执行count(*)的时候会直接返回总行数,但一旦在select语句上加了where条件,执行效率仍然是很低的,即,MySIAM存储引擎仅仅是在查询某张表的总数据量时的效率较高。
② InnoDB存储引擎即使没有写where条件,它在查询符合条件的总的数据量时仍然是将数据从磁盘中一行一行读出来,然后再进行累积计数,它目前并没有好的优化策略,如果要对count进行优化,只能自己手动进行计数了:比如可以借助redis数据库。
总之,在大数据量的情况下,InnoDB存储引擎执行count操作的效率是非常低的,且目前没有比较好的优化思路,非要优化你可以自己手动计数,可以使用redis数据库相关的功能实现。
【三】count的用法:
① 介绍:count是一个聚合函数,用于记录参数不为null的总的记录数
② 常见用法:count(*)、count(主键)、count(字段)、count(某一个数) ---> count(某一个数)的效果和count(*)、count(主键)一样
③ 在查询某个表的查询记录数的时候,count(*)的效率要高于其他三种。
07 update优化
【一】说明:InnoDB的行锁是根据索引加的锁,而不是针对记录加的锁,并且索引不能失效,否则行锁会升级成表锁,并发性能将会降低。
【二】优化:在使用update语句时,更新条件使用索引字段,而不要使用没有索引的字段,否则并发行锁将会升级成表锁,并发性能降低。