存储引擎
1、概述
存储引擎就是存储数据,建立索引,更新查询数据等等技术的实现方式 。存储引擎是基于表的,而不是基于库的。所以存储引擎也可被称为表类型;
Oracle,SqlServer等数据库只有一种存储引擎。MySQL提供了插件式的存储引擎架构。所以MySQL存在多种存储引擎,可以根据需要使用相应引擎,或者编写存储引擎;
可以通过命令 show engines , 来查询当前数据库支持的存储引擎 :
创建新表时如果不指定存储引擎,那么系统就会使用默认的存储引擎,MySQL5.5之前的默认存储引擎是MyISAM,5.5之后就改为了InnoDB。
查看Mysql数据库默认的存储引擎 , 指令 :show variables like ‘%storage_engine%’ ;
InnoDB与MyISAM的差别(着重以下三点):
特点 | InnoDB | MyISAM |
---|---|---|
事务安全 | 支持 | 不支持 |
锁机制 | 行锁(适合高并发) | 表锁 |
支持外键 | 支持 | 不支持 |
2、InnoDB
InnoDB存储引擎是Mysql的默认存储引擎,但是对比MyISAM的存储引擎,InnoDB写的处理效率差一些,并且会占用更多的磁盘空间以保留数据和索引。
InnoDB存储引擎不同于其他存储引擎的特点:
事务控制
InnoDB存储引擎提供了具有提交、回滚、崩溃恢复能力的事务安全:
start transaction;
#插入后,在另一台机器上访问数据,发现并没有添加进去。
insert into emp(id,name,age,salary) values(null, '光明左使',30,3500);
commit;
测试,发现在InnoDB中是存在事务的 ;
外键约束
MySQL支持外键的存储引擎只有InnoDB , 在创建外键的时候, 要求父表必须有对应的索引 , 子表在创建外键的时候, 也会自动的创建对应的索引;
存储方式
InnoDB 存储表和索引有以下两种方式 :
-
使用共享表空间存储, 这种方式创建的表的表结构保存在.frm文件中, 数据和索引保存在 innodb_data_home_dir 和 innodb_data_file_path定义的表空间中,可以是多个文件。
-
使用多表空间存储, 这种方式创建的表的表结构仍然存在 .frm 文件中,但是每个表的数据和索引单独保存在 .ibd 中。
MySql的左连接,右连接,内连接,全连接
本地数据库中有两张表 tcount_tbl 和 runoob_tbl。两张数据表数据如下:
左连接(left join)
LEFT JOIN 会读取左边数据表的全部数据,即便右边表无对应数据;
右连接(right join)
RIGHT JOIN 会读取右边数据表的全部数据,即便左边边表无对应数据;
内连接(inner join)
获取两个表中字段匹配关系的记录;
全连接(full join)
mysql不支持全连接,但可以通过左外连接+ union+右外连接实现;
事务(ACID)
事务一般都是以下四点:
- 原子性(Atomicity)
- 一致性 (Consistency)
- 隔离性 (Isolation)
- 持续性 (Durability)
1、原子性(Atomicity)
原子是世界上的最小单位,具有不可分割性;
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做 ;
2、一致性 (Consistency)
ACID里的AID都是数据库的特征,也就是依赖数据库的具体实现。而唯独这个C,实际上它依赖于应用层,也就是依赖于开发者。(这里的一致性是指系统从一个正确的状态,迁移到另一个正确的状态。什么叫正确的状态呢?就是当前的状态满足预定的约束就叫做正确的状态。) 而事务具备ACID里C的特性是说通过事务的AID来保证我们的一致性.
这里我们举个大家都在说的财务系统的例子:
A要向B支付100元,而A的账户中只有90元,并且我们给定账户余额这一列的约束是,不能小于0。那么很明显这条事务执行会失败,因为90-100=-10,小于我们给定的约束了。
这个例子里,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证。然后我们再看个例子
A要向B支付100元,而A的账户中只有90元,我们的账户余额列没有任何约束。但是我们业务上不允许账户余额小于0,因此支付完成后我们会检查A的账户余额,发现余额小于0了,于是我们进行了事务的回滚。
这个例子里,如果事务执行成功,虽然没有破坏数据库的约束,但是破坏了我们应用层的约束。而事务的回滚保证了我们的约束,因此也可以说事务提供了一致性保证(ps:事实上,是我们应用层利用事务回滚保证了我们的约束不被破坏)。最后我们再看个例子
A要向B支付100元,而A的账户中只有90元,我们的账户余额列没有任何约束。然后支付成功了。
这里,如果按照很多人的理解,事务不是保证一致性么?直观上账户余额为什么能为负呢。但这里事务执行前和执行后,我们的系统没有任何的约束被破坏。一直都是保持正确的状态。
所以,综上。你可以理解一致性就是:应用系统从一个正确的状态到另一个正确的状态。而ACID就是说事务能够通过AID来保证这个C的过程。C是目的,AID都是手段。
3、隔离性 (Isolation)
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
4、持续性 (Durability)
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
事务的隔离级别
CREATE TABLE `demo` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `demo_01`.`demo`(`id`, `name`) VALUES (1, 'a');
INSERT INTO `demo_01`.`demo`(`id`, `name`) VALUES (5, 'b');
INSERT INTO `demo_01`.`demo`(`id`, `name`) VALUES (7, 'c');
INSERT INTO `demo_01`.`demo`(`id`, `name`) VALUES (9, 'd');
Read uncommitted(读未提交)
如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据;
但还是可能会出现脏读:
事务一 | 事务二 |
---|---|
![]() | ![]() |
Read committed(读已提交)
如果是一个读事务,则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
但还是可能会出现不可重复读(重复读数据就变了):
事务一 | 事务二 |
---|---|
![]() | ![]() |
Repeatable read(可重复读)
可重复读取是指在一个事务内,多次读同一个数据,读到的数据是一样的,因此称为是可重复读隔离级别。
但是在使用当前读的情况下还会出现幻读(其实也会出现不可重复读,不过根据官方的话来说MySQL是允许这样的):
这不是bug,而是刻意为之
“This is not a bug but an intended and documented behavior.”
事务一 | 事务二 |
---|---|
![]() | ![]() |
Serializable(可串行化)
提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用!
在MYSQL数据库中,支持上面四种隔离级别,默认的为Repeatable read(可重复读);而在Oracle数据库中,只支Serializeble(串行化)级别和Read committed(读已提交)这两种级别,其中默认的为Read committed级别;
在MYSQL数据库中查看或修改当前事务的隔离级别:
#设置事务隔离级别为可重复读
set session transaction isolation level REPEATABLE READ;
#查看当前事务隔离级别
show VARIABLES like 'transaction_isolation';
为什么一个事务能读取到其他事务修改后未提交的数据?
在没有正式提交之前,数据是先更新到日志之中的,只有commit提交之后,才真正再从日志中更新到mysql表中;
当设置事务隔离级别为Read Ucommitted的时候,一个事务(A)能读取到另一个事务(B)修改后【未提交】的数据。
不可重复读,幻读和脏读
脏读 :
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问这个数据,然后使用了这个数据。
不可重复读 :
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据并进行修改。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,第一个事务两次读到的的数据可能是不一样的。
幻读 :
一个事务在前后两次查询同一个范围的时候、后一次查询看到了前一次查询未看到的行
注意:
- 在可重复读隔离级别下、普通查询是快照读、不会看到别的事务插入数据, 所以、只会在当前读下才会出现幻读;
- 幻读仅指新插入的行被读到、修改后满足条件而被读取到,不能称为幻读;
快照读与当前读
当前读:select...lock in share mode; select...for update;
当前读:update、insert、delete
快照读:select
快照读
当执行select的时候,innodb默认会执行快照读,相当于就是给目前的数据照了一张照片,以后执行select 的时候就会返回当前照片里面的数据,当其他事务提交了也对你不造成影响,和你没关系,这就实现了可重复读了。
那这个照片是什么时候生成的呢?不是开启事务的时候,是当你第一次执行select的时候,也就是说,当A开启了事务,然后没有执行任何操作,这时候B insert了一条数据然后commit,这时候A执行 select,那么返回的数据中就会有B添加的那条数据…之后无论再有其他事务commit都没有关系,因为照片已经生成了,而且不会再生成了,以后都会参考这张照片!
当前读
当前读会读取最新的记录,也就是说别的事务虽然还没提交的数据你也可以看到;
索引
什么是索引
索引在MySQL中也叫做“键”或者"key"(primary key,unique key,还有一个index key),是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要,减少io次数,加速查询。(其中primary key和unique key,除了有加速查询的效果之外,还有约束的效果,primary key 不为空且唯一,unique key 唯一,而index key只有加速查询的效果,没有约束效果)
索引相当于字典的音序表,如果要查某个字,如果不使用音序表,则需要从几百页中逐页去查。
一旦为表创建了索引,以后的查询最好先查索引,再根据索引定位的结果去找数据
示例:
SELECT * FROM Employee WHERE Employee_Name = 'Jesus'
一旦我们运行这个查询,在查找名字为Jesus的雇员的过程中,究竟会发生什么?数据库不得不去遍历Employee表中的每一行并确定雇员的名字(Employee_Name)是否为 ‘Jesus’。由于我们想要得到每一个名字为Jesus的雇员信息,在查询到第一个符合条件的行后,不能停止查询,因为可能还有其他符合条件的行。所以,必须一行一行的查找直到最后一行,这就意味数据库不得不检查上千行数据才能找到所以名字为Jesus的雇员。这就是所谓的全表扫描。
索引分类
1) 单值索引 :即一个索引只包含单个列,一个表可以有多个单列索引
2) 唯一索引 :索引列的值必须唯一,但允许有空值
3) 复合索引 :即一个索引包含多个列
聚集索引与普通索引
InnoDB有两大类索引:
- 聚集索引(Clustered Index):数据的物理存放顺序与索引顺序是一致的,叶子节点存储行记录,默认通过主键聚集数据,如果没有定义主键,则选择第一个非空的唯一索引,如果没有非空唯一索引,则选择rowid来作为聚集索引;
- 非聚集索引(Secondary Index):叶子节点存储主键值,除此之外的表上的每个非聚簇索引都是二级索引,又叫辅助索引;
所以,对于聚集索引来说,你创建主键的时候,自动就创建了主键的聚集索引,并且不管怎样都会有聚集索引存在!
- normal:表示普通索引;
- unique:表示唯一的,不允许重复的索引,如果该字段信息保证不会重复例如身份证号用作索引时,可设置为unique;
- full textl: 表示 全文搜索的索引, FULLTEXT用于搜索很长一篇文章的时候,效果最好。用在比较短的文本,如果就一两行字的,普通的INDEX也可以;
总结,索引的类别由建立索引的字段内容特性来决定,通常normal最常见。
索引覆盖
如果一个索引包含(或覆盖)所有需要查询的字段的值,称为‘覆盖索引’。即只需扫描索引而无须回表。
回表:先通过普通索引定位到主键值(普通索引叶子节点存储的是主键值),在通过聚集索引定位到行记录。这就是所谓的回表查询,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。
索引的使用操作
创建索引
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name [USING index_type] ON tbl_name(index_col_name,...)
示例:
CREATE INDEX index_city_name ON city(city_name);
查看索引
show index from table_name;
示例:
show INDEX from city;
删除索引
DROP INDEX index_name ON tbl_name;
示例:
DROP INDEX index_city_name ON city;
ALTER命令
1). alter table tb_name add primary key(column_list);
该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL
2). alter table tb_name add unique index_name(column_list);
这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)
3). alter table tb_name add index index_name(column_list);
添加普通索引, 索引值可以出现多次。
4). alter table tb_name add fulltext index_name(column_list);
该语句指定了索引为FULLTEXT, 用于全文索引
避免索引失效
1). 全值匹配 ,对索引中所有列都指定具体值。
该情况下,索引生效,执行效率高。
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
2). 最左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。
匹配最左前缀法则,走索引:
explain select * from tb_seller where name='小米科技';
explain select * from tb_seller where name='小米科技' and status='1';
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
违法最左前缀法则 , 索引失效:
explain select * from tb_seller where status='1';
explain select * from tb_seller where status='1' and address='北京市';
如果符合最左法则,但是出现跳跃某一列,只有最左列索引生效:
explain select * from tb_seller where name='小米科技' and address='北京市';
最左前缀法则就像上楼一样,不先上一楼怎么上二楼三楼,上了一楼,又不上二楼怎么上三楼!
3). 范围查询右边的列,不能使用索引
#address索引失效
explain select * from tb_seller where name='小米科技' and status>'1' and address='北京市';
根据前面的两个字段name , status 查询是走索引的, 但是最后一个条件address 没有用到索引。
4). 不要在索引列上进行运算操作, 索引将失效
explain select * from tb_seller where SUBSTR(name,3,2)='科技';
5). 字符串不加单引号,造成索引失效
#name,status 是走索引的
explain select * from tb_seller where name='小米科技' and status='1';
#只有name走索引了
explain select * from tb_seller where name='小米科技' and status=1;
原因:在查询时如果没有对字符串加单引号,MySQL的查询优化器,会自动的进行类型转换,造成索引失效。
6). 尽量使用覆盖索引,避免select
只访问索引的查询(索引列完全包含查询列) 。
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
explain select name from tb_seller where name='小米科技' and status='1' and address='北京市';
explain select name,status from tb_seller where name='小米科技' and status='1' and address='北京市';
explain select name,status,address from tb_seller where name='小米科技' and status='1' and address='北京市';
如果查询列,超出索引列,也会降低性能:
#password 字段还需要回表查询
explain select name,status,address,password from tb_seller where name='小米科技' and status='1' and address='北京市';
7). 用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到
示例,name字段是索引列 ,而createtime不是索引列,中间是or进行连接是不走索引的 :
explain select * from tb_seller where name='黑马程序员' or createtime = '2088-01-01 12:00:00';
8). 以%开头的Like模糊查询,索引失效
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
#索引失效
explain select * from tb_seller where name like '%小米科技';
#走索引
explain select * from tb_seller where name like '小米科技%';
#索引失效
explain select * from tb_seller where name like '%小米科技%';
9). 如果MySQL评估使用索引比全表更慢,则不使用索引
#创建address索引
CREATE INDEX index_address ON tb_seller(address);
#不走address索引
explain select * from tb_seller where address = '北京市';
#走address索引
explain select * from tb_seller where address = '西安市';
原因如下图所示:
10). is NULL , is NOT NULL 有时索引会失效(原因与第9点一样)
11). IN 走索引, NOT IN 索引失效
#走索引
explain select * from tb_seller where name IN ('小米科技');
#不走索引
explain select * from tb_seller where name NOT IN ('小米科技');
12). 单列索引和复合索引
尽量使用复合索引,而少使用单列索引 。
create index idx_name_sta_address on tb_seller(name, status, address);
创建复合索引 就相当于创建了三个索引 :
name
name + status
name + status + address
创建单列索引 :
create index idx_seller_name on tb_seller(name);
create index idx_seller_status on tb_seller(status);
create index idx_seller_address on tb_seller(address);
数据库会选择一个最优的索引(辨识度最高索引)来使用,并不会使用全部索引 。
Explain关键字
可以通过 EXPLAIN命令获取 MySQL如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。
查看SQL语句的执行计划 :
explain select * from tb_item where id = 1;
比较简单的几个列放在表格里,如下:
列 | 描述 |
---|---|
id | id列的编号是 select 的序列号,有几个 select 就有几个id,并且id的顺序是按 select 出现的顺序增的。id列越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行 |
possible_keys | 这一列显示查询可能使用哪些索引来查找 |
key | 这一列显示mysql实际采用哪个索引来优化对该表的访问。如果没有使用索引,则该列是 NULL |
rows | 这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数 |
filtered | 返回结果的行占需要读到的行(rows列的值)的百分比 |
key_len | 索引长度,通过这个值可以算出具体使用了索引中的哪些列; |
select_type列常见的取值,如下表所示:
select_type | 含义 |
---|---|
SIMPLE | 简单的select查询,查询中不包含子查询或者UNION |
PRIMARY | 查询中若包含任何复杂的子查询,最外层查询标记为该标识 |
SUBQUERY | 在SELECT 或 WHERE 列表中包含了子查询 |
DERIVED | 在FROM 列表中包含的子查询,被标记为 DERIVED(衍生) MYSQL会递归执行这些子查询,把结果放在临时表中 |
UNION | 若第二个SELECT出现在UNION之后,则标记为UNION ; 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为DERIVED |
UNION RESULT | 从UNION表获取结果的SELECT |
type列 显示的是访问类型,是较为重要的一个指标,可取值为:
type | 含义 |
---|---|
NULL | MySQL不访问任何表,索引,直接返回结果 |
system | 表只有一行记录(等于系统表),这是const类型的特例,一般不会出现 |
const | 表示通过索引一次就找到了,const 用于比较primary key 或者 unique 索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL 就能将该查询转换为一个常亮。const于将 “主键” 或 “唯一” 索引的所有部分与常量值进行比较 |
eq_ref | 类似ref,区别在于使用的是唯一索引,使用主键的关联查询,关联查询出的记录只有一条。常见于主键或唯一索引扫描 |
ref | 非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,返回所有匹配某个单独值的所有行(多个) |
range | 只检索给定范围的行,使用一个索引来选择行。一般就是在你的where语句中出现了between、<、>、in等,这种范围索引扫描比全索引扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。 |
index | 全索引扫描(当查询的结果全为索引列的时候,虽然也是全部扫描,但是只查询的索引库,而没有去查询数据) |
all | 全表扫描 |
结果值从最好到最坏以此是:NULL > system > const > eq_ref > ref > range > index > ALL
extra列展示其他的额外的信息,主要有以下几种:
extra | 含义 |
---|---|
using filesort | 当我们的query中包含order by操作,而且无法利用索引完成排序操作时,Mysql Query Optimizer不得不选择相应的排序算法来实现,效率低 |
using temporary | mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索引来优化。 效率低 |
using index | 查询的列被索引覆盖,并且where筛选条件是索引的前导列 |
using index condition | 查询的列不全在索引中,where条件中是一个前导列的范围 |
Using where | 查询的列未被索引覆盖,where筛选条件非索引的前导列 |
Using where;Using index | 查询的列被索引覆盖,where筛选条件不是索引的前导列或者是一个前导列的范围 |
所谓前导列,就是在创建复合索引语句的第一列或者连续的多列。比如通过:CREATE INDEX comp_ind ON table1(x, y, z)创建索引,那么x,xy,xyz都是前导列,而yz,y,z这样的就不是(其实就是上面说的最左前缀法则);
show profile
show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。
#查看当前MySQL是否支持profile
SELECT @@have_profiling;
#查看当前MySQL是否支持profile
SELECT @@profiling;
#开启profiling 开关
set profiling=1;
通过profile,我们能够更清楚地了解SQL执行的过程,例如:我们可以执行以下一系列的操作
show databases;
use demo_01;
show tables;
select * from tb_item where id < 5;
select count(*) from tb_item;
再执行show profiles 指令, 来查看SQL语句执行的耗时:
通过show profile for query query_id 语句可以查看到该SQL执行过程中每个线程的状态和消耗的时间:
注意:Sending data 状态表示MySQL线程开始访问数据行并把结果返回给客户端,而不仅仅是返回个客户端。由于在Sending data状态下,MySQL线程往往需要做大量的磁盘读取操作,所以经常是整各查询中耗时最长的状态
在获取到最消耗时间的线程状态后,MySQL支持进一步选择all、cpu、block io 、context switch、page faults等明细类型类查看MySQL在使用什么资源上耗费了过高的时间。例如,选择查看CPU的耗费时间 :
各个字段的含义:
字段 | 含义 |
---|---|
Status | sql 语句执行的状态 |
Duration | sql 执行过程中每一个步骤的耗时 |
CPU_user | 当前用户占有的cpu |
CPU_system | 系统占有的cpu |
SQL优化
1、定位低效率执行SQL
可以通过以下两种方式定位执行效率较低的 SQL 语句:
- 慢查询日志:通过慢查询日志定位那些执行效率较低的 SQL 语句,用–log-slow-queries[=file_name]选项启动时,mysql会写一个包含所有执行时间超过 long_query_time 秒的 SQL 语句的日志文件;
- show processlist:查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时地查看 SQL 的执行情况,同时对一些锁表操作进行优化
对应字段释义如下:
1) id列:用户登录mysql时,系统分配的"connection_id",可以使用函数connection_id()查看
2) user列:显示当前用户。如果不是root,这个命令就只显示用户权限范围的sql语句
3) host列:显示这个语句是从哪个ip的哪个端口上发的,可以用来跟踪出现问题语句的用户
4) db列:显示这个进程目前连接的是哪个数据库
5) command列:显示当前连接的执行的命令,一般取值为休眠(sleep),查询(query),连接(connect)等
6) time列:显示这个状态持续的时间,单位是秒
7) state列:显示使用当前连接的sql语句的状态,很重要的列。state描述的是语句执行中的某一个状态。一个sql语句,以查询为例,可能需要经过copying to tmp table、sorting result、sending data等状态才可以完成
8) info列:显示这个sql语句,是判断问题语句的一个重要依据
2、优化insert语句
使用多个值表的insert语句,这种方式将大大的缩减客户端与数据库之间的连接、关闭等消耗,示例:
#原始方式为:
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
#优化后的方案为 :
insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
数据有序插入:
#原始方式为:
insert into tb_test values(4,'Tim');
insert into tb_test values(1,'Tom');
insert into tb_test values(3,'Jerry');
insert into tb_test values(5,'Rose');
insert into tb_test values(2,'Cat');
#优化后的方案为 :
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
insert into tb_test values(4,'Tim');
insert into tb_test values(5,'Rose');
2、优化select语句
-
不要使用 count(列名)或 count(常量)来替代 count(*);
-
拒绝大SQL(可能一条大SQL就把整个数据库堵死),大SQL要拆解成多条简单SQL;
-
同一字段,使用in()代替or;
-
in 操作能避免则避免,若实在避免不了,需要控制在 1000 个之内;
-
在表查询中,不能使用 * 作为查询的字段列表,需要哪些字段必须明确写明(1、无用字段增加网络消耗,尤其是 text 类型并且数据特别大的字段。2、* 需要回表);
-
对于函数运算或者排序分组啥的尽量放在业务代码层面进行,避免写在SQL里;
-
select 查询能过滤最多的条件放在前面;
-
嵌套子查询(Mysql需要创建临时表来存储中间数据)改为join查询或者拆分成多个简单查询;
#优化前 explain select * from t_user where id in (select user_id from user_role ); #优化后 explain select * from t_user u , user_role ur where u.id = ur.user_id;
-
对于需要频繁查询的字段建立索引;
-
建立了相应索引,也需要检查SQL语句,避免错误使用出现索引失效的情况;
-
分页使用物理分页,而非内存分页;
-
order by对应字段要建立索引,并且使用索引顺序要符合最左前缀法则;
-
避免超过三个表 join,需要 join 的字段,数据类型保持绝对一致,多表关联查询时,保证被关联的字段(内层表的字段)需要有索引;对于复杂的join语句尽量拆分成多个简单查询语句;如果要使用 join,应该选择小表做驱动表(外层表)
join原理:https://zhuanlan.zhihu.com/p/54275505
3、使用SQL提示
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
- USE INDEX:在查询语句中表名的后面,添加 use index 来提供希望MySQL去参考的索引列表,就可以让MySQL不再考虑其他可用的索引;
- IGNORE INDEX:只是单纯的想让MySQL忽略一个或者多个索引,则可以使用 ignore index;
- FORCE INDEX:为强制MySQL使用一个特定的索引,可在查询中使用 force index;
示例:
EXPLAIN SELECT * from tb_seller use index(index_name) WHERE name = '小米科技';
explain select * from tb_seller ignore index(index_name) where name = '小米科技';
explain select * from tb_seller FORCE index(index_name) where name = '小米科技';