6.1:insert的优化:
(1)普通的插入数据
 
 如果我们需要一次性往数据库表中插入多条记录,可以从以下三个方面进行优化。 
 
insert into tb_test values(1,'tom');
insert into tb_test values(2,'cat');
insert into tb_test values(3,'jerry'); 
  1).  
  优化方案一  
 
 
  
  批量插入数据 
 
 
 Insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry'); 
   2).  
   优化方案二  
  
 
   
   手动控制事务 
  
 
  start transaction;
insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
insert into tb_test values(4,'Tom'),(5,'Cat'),(6,'Jerry');
insert into tb_test values(7,'Tom'),(8,'Cat'),(9,'Jerry');
commit; 
    3).  
    优化方案三 
   
 
    
    主键顺序插入,性能要高于乱序插入。 
   
 
    
    
    主键乱序插入  
    : 8 1 9 21 88 2 4 15 89 5 7 3  
   
 
    
    主键顺序插入  
    : 1 2 3 4 5 7 8 9 15 21 88 89 
   
 
    
   (2)大量的插入数据:
 
    如果一次性需要插入大批量数据 
    ( 
    比如 
    :  
    几百万的记录 
    ) 
    ,使用 
    insert 
    语句插入性能较低,此时可以使  
   
 
    
    用 
    MySQL 
    数据库提供的 
    load 
    指令进行插入。操作如下: 
   
 
    
   
 
    可以执行如下指令,将数据脚本文件中的数据加载到表结构中: 
   
 
    
   -- 客户端连接服务端时,加上参数 -–local-infile
mysql –-local-infile -u root -p
-- 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
-- 执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql1.log' into table tb_user fields
terminated by ',' lines terminated by '\n' ; 
    主键顺序插入性能高于乱序插入 
   
 
    
    
    示例演示 
    :  
   
 
    
    A.  
    创建表结构 
   
 
   CREATE TABLE `tb_user` (
`id` INT(11) 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(1) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_user_username` (`username`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 ; 
     B.  
     设置参数 
    
 
    -- 客户端连接服务端时,加上参数 -–local-infile
mysql –-local-infile -u root -p
-- 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1; 
      C. load 
      加载数据 
      
 
    load data local infile '/root/load_user_100w_sort.sql' into table tb_user
fields terminated by ',' lines terminated by '\n' ;
 
    我们看到,插入 
    100w 
    的记录, 
    17s 
    就完成了,性能很好。  
   
 
    
    在 
    load 
    时,主键顺序插入性能高于乱序插入。 
   
 
    
    
   6.2,主键优化:
 
    在上一小节,我们提到,主键顺序插入的性能是要高于乱序插入的。 这一小节,就来介绍一下具体的原因,然后再分析一下主键又该如何设计。 
   
 
    
    
    1).  
    数据组织方式  
   
 
    
    在 
    InnoDB 
    存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表  
   
 
    
    (index organized table IOT) 
    。 
   
 
   
 
    行数据,都是存储在聚集索引的叶子节点上的。而我们之前也讲解过 
    InnoDB 
    的逻辑结构图: 
   
 
   
 
    在 
    InnoDB 
    引擎中,数据行是记录在逻辑结构  
    page  
    页中的,而每一个页的大小是固定的,默认 
    16K 
    。那也就意味着, 一个页中所存储的行也是有限的,如果插入的数据行row 
    在该页存储不小,将会存储到下一个页中,页与页之间会通过指针连接。 
   
 
    
    
    2).  
    页分裂 
   
 
    
    页可以为空,也可以填充一半,也可以填充 
    100% 
    。每个页包含了 
    2-N 
    行数据 
    ( 
    如果一行数据过大,会行溢出) 
    ,根据主键排列。 
   
 
    
    
    A.  
    主键顺序插入效果  
   
 
    
    ① 
    .  
    从磁盘中申请页, 主键顺序插入 
   
 
    
 
   ②. 第一个页没有满,继续往第一页插入

③. 当第一个也写满之后,再写入第二个页,页与页之间会通过指针连接

④. 当第二页写满了,再往第三页写入

 
     B.  
     主键乱序插入效果  
    
 
     
     ① 
     .  
     加入 
     1#,2# 
     页都已经写满了,存放了如图所示的数据 
    
 
     
   
 
    ② 
    .  
    此时再插入 
    id 
    为 
    50 
    的记录,我们来看看会发生什么现象  
   
 
    
    会再次开启一个页,写入新的页中吗? 
   
 
    
 
    
    不会。因为,索引结构的叶子节点是有顺序的。按照顺序,应该存储在 
    47 
    之后。 
   
 
    
    
 
    
     但是 
     47 
     所在的 
     1# 
     页,已经写满了,存储不了 
     50 
     对应的数据了。 那么此时会开辟一个新的页  
     3# 
    
 
     
 
     
     但是并不会直接将 
     50 
     存入 
     3# 
     页,而是会将 
     1# 
     页后一半的数据,移动到 
     3# 
     页,然后在 
     3# 
     页,插入 
     50 
     。 
    
 
     
     
 
     
      移动数据,并插入 
      id 
      为 
      50 
      的数据之后,那么此时,这三个页之间的数据顺序是有问题的。  
      1# 
      的下一个页,应该是3# 
      ,  
      3# 
      的下一个页是 
      2# 
      。 所以,此时,需要重新设置链表指针 
     
 
     
 
     
     上述的这种现象,称之为  
     " 
     页分裂 
     " 
     ,是比较耗费性能的操作。 
    
 
     
     
     
      3).  
      页合并  
     
 
      
      目前表中已有数据的索引结构 
      ( 
      叶子节点 
      ) 
      如下 
     
 
      
 
      
       当我们对已有数据进行删除时,具体的效果如下 
       :  
      
 
       
       当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记( 
       flaged 
       )为删除并且它的空间变得允许被其他记录声明使用。 
      
 
      
 
     
      当我们继续删除 
      2# 
      的数据记录 
     
 
     
 
     
      当页中删除的记录达到  
      MERGE_THRESHOLD 
      (默认为页的 
      50% 
      ), 
      InnoDB 
      会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。 
     
 
     
 
     
      删除数据,并将页合并之后,再次插入新的数据 
      21 
      ,则直接插入 
      3# 
      页 
     
 
     
 
     
      这个里面所发生的合并页的这个现象,就称之为  
      " 
      页合并 
      " 
      。 
     
 
      
       知识小贴士:  
      
 
       
       
       MERGE_THRESHOLD 
       :合并页的阈值,可以自己设置,在创建表或者创建索引时指定。 
      
 
      
      4).  
      索引设计原则  
     
 
      
      满足业务需求的情况下,尽量降低主键的长度。  
     
 
      
      插入数据时,尽量选择顺序插入,选择使用 
      AUTO_INCREMENT 
      自增主键。 
     
 
     
     尽量不要使用UUID 
     做主键或者是其他自然主键,如身份证号。  
    
 
     
      业务操作时,避免对主键的修改。 
     
 
    6.3,order by优化:
 
     MySQL 
     的排序,有两种方式:  
    
 
     
     Using filesort :  
     通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 
     sort  
    
 
     
     buffer 
     中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫  
     FileSort  
     排序。  
    
 
     
     Using index :  
     通过有序索引顺序扫描直接返回有序数据,这种情况即为  
     using index 
     ,不需要  
    
 
     
     额外排序,操作效率高。  
    
 
     
     
     对于以上的两种排序方式, 
     Using index 
     的性能高,而 
     Using filesort 
     的性能低,我们在优化排序  
    
 
     
     操作时,尽量要优化为  
     Using index 
     。  
    
 
     
     
     接下来,我们来做一个测试:  
    
 
     
     A.  
     数据准备  
    
 
     
     把之前测试时,为 
     tb_user 
     表所建立的部分索引直接删除掉 
    
 
     
    drop index idx_user_phone on tb_user;
drop index idx_user_phone_name on tb_user;
drop index idx_user_name on tb_user;
 
      B.  
      执行排序 
      SQL 
     
 
     explain select id ,age,phone from tb_user order by age;
explain select id,age,phone from tb_user order by age, phone ;
 
      由于  
      age, phone  
      都没有索引,所以此时再排序时,出现 
      Using filesort 
      , 排序性能较低。 
     
 
      
     
      C.  
      创建索引 
     
 
      
    -- 创建索引
create index idx_user_age_phone_aa on tb_user(age,phone); 
      D.  
      创建索引后,根据 
      age, phone 
      进行升序排序 
     
 
     explain select id,age,phone from tb_user order by age; 
 
     
      建立索引之后,再次进行排序查询,就由原来的 
      Using filesort 
      , 变为了  
      Using index 
      ,性能  
     
 
      
      就是比较高的了。 
     
 
      
      
       E.  
       创建索引后,根据 
       age, phone 
       进行降序排序 
      
 
      explain select id,age,phone from tb_user order by age desc , phone desc ;
 
      也出现  
      Using index 
      , 但是此时 
      Extra 
      中出现了  
      Backward index scan 
      ,这个代表反向扫描索  
     
 
      
      引,因为在 
      MySQL 
      中我们创建的索引,默认索引的叶子节点是从小到大排序的,而此时我们查询排序时,是从大到小,所以,在扫描时,就是反向扫描,就会出现 Backward index scan 
      。 在  
     
 
      
      MySQL8 
      版本中,支持降序索引,我们也可以创建降序索引。 
     
 
      
     
      F.  
      根据 
      phone 
      , 
      age 
      进行升序排序, 
      phone 
      在前, 
      age 
      在后。 
     
 
      
    explain select id,age,phone from tb_user order by phone , age; 
      排序时 
      , 
      也需要满足最左前缀法则 
      , 
      否则也会出现  
      filesort 
      。因为在创建索引的时候,  
      age 
      是第一个  
     
 
      
      字段, 
      phone 
      是第二个字段,所以排序时,也就该按照这个顺序来,否则就会出现  
      Using  
     
 
      
      filesort 
      。 
     
 
      
      
      F.  
      根据 
      age, phone 
      进行降序一个升序,一个降序 
     
 
     explain select id ,age,phone from tb_user order by age asc ,phone desc;
 
       因为创建索引时,如果未指定顺序,默认都是按照升序排序的,而查询时,一个升序,一个降序,此时就会出现Using filesort 
       。 
      
 
       
       
 
       
       为了解决上述的问题,我们可以创建一个索引,这个联合索引中  
       age  
       升序排序, 
       phone  
       倒序排序 
      
 
       
     create index idx_user_age_phone on tb_user(age asc,phone desc);
 
       H.  
       然后再次执行如下 
       SQL 
      
 
       
      explain select id,age,phone from tb_user order by age asc , phone desc ;
 
      升序 
      / 
      降序联合索引结构图示 
      : 
     
 
      
 
      
 
      
       由上述的测试 
       , 
       我们得出 
       order by 
       优化原则 
       :  
      
 
       
       A.  
       根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。  
      
 
       
       B.  
       尽量使用覆盖索引。  
      
 
       
       C.  
       多字段排序 
       ,  
       一个升序一个降序,此时需要注意联合索引在创建时的规则( 
       ASC/DESC 
       )。  
      
 
       
       D.  
       如果不可避免的出现 
       filesort 
       ,大数据量排序时,可以适当增大排序缓冲区大小  
      
 
       
       sort_buffer_size( 
       默认 
       256k) 
       。 
      
 
     


















