后端开发的必备基础。

参考链接:数据库常见面试题(开发者篇)

1.1 SQL语言的功能有哪些

SQL是结构化查询语言,其功能包括数据査询、数据操纵、数据定义和数据控制4个部分。

基本的 SQL 语句有 select、insert、update、delete、create、drop、grant、revoke等。
SQL

drop、delete与truncate分别在什么场景之下使用?
drop table

  • 1)属于DDL
  • 2)不可回滚
  • 3)不可带where
  • 4)表内容和结构删除
  • 5)删除速度快

truncate table

  • 1)属于DDL
  • 2)不可回滚
  • 3)不可带where
  • 4)表内容删除
  • 5)删除速度快

delete from

  • 1)属于DML
  • 2)可回滚
  • 3)可带where
  • 4)表结构在,表内容要看where执行的情况
  • 5)删除速度慢,需要逐行删除

不再需要一张表的时候,用drop
想删除部分数据行时候,用delete,并且带上where子句
保留表而删除所有数据的时候用truncate

注意:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。

也是这样所以Delete 删除的数据是可以恢复的。

drop、truncate和delete的区别详细补充

例题:
Oracle数据库的一个表中有若干条数据,其占用的存储空间为10MB,如果用delete语句删除表中的所有数据,此时该表所占存储空间为多大?

答案:10MB。数据库中delete操作类似于在Windows系统中把数据放到回收站,还可以恢复, 因此它不会立即释放所占的存储空间。如果想在删除数据后立即释放存储空间,可以使用truncate。

1.2 内连接与外连接有什么区别

内连接,也被称为自然连接,只有两个表相匹配的行才能在结果集中出现。返回的结果集选取了两个表中所有相匹配的数据,舍弃了不匹配的数据,所以内连接可能会造成信息的丢失。
select fieldlist from table 1 [ inner] join table2 on table 1. column = table2. column

外连接不仅包含符合连接条件的行,而且还包括左表(左外连接时)、右表(右外连接时)或两个边接表(全外连接)中的所有数据行。

示例:
student courses
1.内连接

select * from student,courses where student.`学号` = courses.`学号`;
select * from student join courses on student.`学号` = courses.`学号`;

innerResult

2.外连接

1)左连接

select * from student left join courses on student.`学号` = courses.`学号`;

leftResult
2)右连接

select * from student right join courses on student.`学号` = courses.`学号`;

rightResult
3)全连接
MySQL目前不支持此种方式。

1.3 什么是事务

事务是数据库中一个单独的执行单元(Unit)。当在数据库中更改数据成功时,在事务中更改的数据便会提交,不再改变。否则,事务就取消或者回滚,更改无效。

事务必须满足4个属性,即原子性(atomicity)、一致性(consistency)、隔离性(isola­ tion)、持久性(durability),即ACID4种属性。

  1. 原子性(Atomicity):事务作为一个整体被执行 ,要么全部执行,要么全部不执行
  2. 一致性(Consistency):保证数据库状态从一个一致状态转变为另一个一致状态
  3. 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行
  4. 持久性(Durability):一个事务一旦提交,对数据库的修改应该永久保存。

示例:

A和B账户都有1000块,现在我让A账户向B账号转500块钱(JDBC默认的情况下是关闭事务的

//A账户减去500元
String sql = "update money set money = money-500 where name = 'A'";
PreparedStatement preparedStatement = c.prepareStatement(sql);
preparedStatement.executeUpdate();

//B账户增加500元
String sql2 = "update money set money = money+500 where name = 'B'";
PreparedStatement preparedStatement2 = c.prepareStatement(sql2);
preparedStatement2.executeUpdate();

我们的确可以发现A向B转账,成功了。可是如果A向B转账的过程中出现了问题呢?下面模拟一下

//A账户减去500元
String sql = "update money set money = money-500 where name = 'A'";
PreparedStatement preparedStatement = c.prepareStatement(sql);
preparedStatement.executeUpdate();

//出现问题
int a = 3/0;

//B账户增加500元
String sql2 = "update money set money = money+500 where name = 'B'";
PreparedStatement preparedStatement2 = c.prepareStatement(sql2);
preparedStatement2.executeUpdate();

上面代码是会抛出异常的,我们再来查询一下数据。A账户少了500块钱,B账户的钱没有增加。这明显是不合理的。
updateMoney

通过事务来解决上面出现的问题:

// 开启事务,对数据的操作就不会立即生效。
c.setAutoCommit(false);

//A账户减去500元
String sql = "update money set money = money-500 where name = 'A'";
PreparedStatement preparedStatement = c.prepareStatement(sql);
preparedStatement.executeUpdate();

//出现问题
int a = 3/0;

//B账户增加500元
String sql2 = "update money set money = money+500 where name = 'B'";
PreparedStatement preparedStatement2 = c.prepareStatement(sql2);
preparedStatement2.executeUpdate();

// 如果程序能执行到这里,没有抛出异常,我们就提交数据
c.commit();

// 关闭事务【自动提交】
c.setAutoCommit(true);

} catch (SQLException e) {
// TODO 自动生成的 catch 块
try {
// 如果出现了异常,就会进到这里来,我们就把事务回滚
c.rollback();

// 关闭事务【自动提交】
c.setAutoCommit(true);

} catch (Exception e2) {
// TODO: handle exception
e.printStackTrace();
}
}

上面的程序也一样抛出了异常,A账户钱没有减少,B账户的钱也没有增加。

注意:当Connection遇到一个未处理的SQLException时,系统会非正常退出,事务也会自动回滚,但如果程序捕获到了异常,是需要在catch中显式回滚事务的。

扩展:
1)事务的并发问题有哪几种?

  1. 丢失更新:一个事务的更新覆盖了另一个事务的更新;
  2. 脏读:一个事务读取了另一个事务未提交的数据;
  3. 不可重复读:不可重复读的重点是修改,同样条件下两次读取结果不同,也就是说,被读取的数据可以被其它事务修改;
  4. 幻读:幻读的重点在于新增或者删除,同样条件下两次读出来的记录数不一样。

2) 事务的隔离级别有哪几种?
隔离级别决定了一个session中的事务可能对另一个session中的事务的影响。ANSI标准定义了4个隔离级别,MySQL的InnoDB都支持,分别是:

  1. 读未提交(READ UNCOMMITTED):最低级别的隔离,通常又称为dirty read,它允许一个事务读取另一个事务还没 commit 的数据,这样可能会提高性能,但是会导致脏读问题;
  2. 读已提交(READ COMMITTED):在一个事务中只允许对其它事务已经 commit 的记录可见,该隔离级别不能避免不可重复读问题;
  3. 可重复读(REPEATABLE READ):在一个事务开始后,其他事务对数据库的修改在本事务中不可见,直到本事务 commit 或 rollback。但是,其他事务的 insert/delete 操作对该事务是可见的,也就是说,该隔离级别并不能避免幻读问题。在一个事务中重复 select 的结果一样,除非本事务中 update 数据库。
  4. 序列化(SERIALIZABLE):最高级别的隔离,只允许事务串行执行。

MySQL默认的隔离级别是可重复读(REPEATABLE READ)
MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关:

  • MyISAM:不支持事务,用于只读程序提高性能;
  • InnoDB:支持ACID事务、行级锁、并发;
  • Berkeley DB:支持事务。

1.4 什么是存储过程?它与函数有什么区别与联系

SQL语句在执行时要先编译,然后再被执行。在大型数据库系统中,为了提高效率,将为 了完成特定功能的SQL语句集进行编译优化后,存储在数据库服务器中,用户通过指定存储 过程的名字来调用执行。

例如:

CREATE DEFINER=`root`@`localhost` PROCEDURE `NewProc`(OUT `getMoney` varchar(11))
BEGIN
select * from money where name = 'A';
END

调用存储过程语法: exec sp_name [参数名]
删除存储过程语法: drop procedure sp_name

存储过程不等于函数,二者虽然本质上没有区别,但还是有如下几个方面 的不同:

1) 存储过程一般是作为一个独立的部分来执行,而函数可以作为查询语句的一个部分来调用。由于函数可以返回一个对象,因此它可以在查询语句中位于From关键字的后面。
2) 一般而言,存储过程实现的功能较复杂,而函数实现的功能针对性较强。
3) 函数需要用括号包住输入的参数,且只能返回一个值或表对象,而存储过程可以返回多个参数。
4) 函数可以嵌入在SQL中使用,可以在select中调用,存储过程则不行。
5) 函数不能直接操作实体表,只能操作内建表。
6) 存储过程在创建时即在服务器上进行了编译,其执行速度比函数快

存储过程的优点:

  • 能够将代码封装起来
  • 保存在数据库之中
  • 让编程语言进行调用
  • 存储过程是一个预编译的代码块,执行效率比较高
  • 一个存储过程替代大量T_SQL语句 ,可以降低网络通信量,提高通信速率

存储过程的缺点:

  • 每个数据库的存储过程语法几乎都不一样,十分难以维护(不通用)
  • 业务逻辑放在数据库上,难以迭代

1.5 三大范式

我们现在需要建立一个描述学校教务的数据库,该数据库涉及的对象包括学生的学号(Sno)、所在系(Sdept)、系主任姓名(Mname)、课程号(Cno)和成绩(Grade),假设我们使用单一的关系模式 Student 来表示,会描述成以下这个样子:
fanshi_1

这个关系模式存在以下问题:
(1) 数据冗余
比如,每一个系的系主任姓名重复出现,重复次数与该系所有学生的所有课程成绩出现次数相同,这将浪费大量的存储空间。
(2)更新异常(update anomalies)
由于数据冗余,当更新数据库中的数据时,系统要付出很大的代价来维护数据库的完整性,否则会面临数据不一致的危险。比如,某系更换系主任后,必须修改与该系学生有关的每一个元组。
(3)插入异常(insertion anomalies)
如果一个系刚成立,尚无学生,则无法把这个系及其系主任的信息存入数据库。
(4)删除异常(deletion anomalies)
如果某个系的学生全部毕业了,则在删除该系学生信息的同时,这个系及其系主任的信息也丢失了。

第一范式:列不可分
1NF(第一范式)是对属性具有原子性的要求,不可再分,例如:
fanshi_2
如果认为最后一列还可以再分成出生年,出生月,出生日,则它就不满足第一范式的要求。

第二范式: 消除非主属性对码的部分函数依赖
2NF(第二范式)是对记录有唯一性的要求,即实体的唯一性,不存在部分依赖,每一列与主键都相关,例如:
fanshi_3
该表明显说明了两个事物:学生信息和课程信息;正常的依赖应该是:学分依赖课程号,姓名依赖学号,但这里存在非主键字段对码的部分依赖,即与主键不相关,不满足第二范式的要求。

正确做法:
fanshi_4

第三范式: 消除非主属性对码的传递函数依赖
3NF(第三范式)对字段有冗余性的要求,任何字段不能由其他字段派生出来,它要求字段没有冗余,即不存在传递依赖。

传递函数依赖:设X,Y,Z是关系R中互不相同的属性集合,存在X→Y(Y !→X),Y→Z,则称Z传递函数依赖于X。

举个例子:通过A得到B,通过B得到C,B得不到A,那么成C传递依赖于A

例如:
fanshi_5
很明显,学院电话是一个冗余字段,存在依赖传递:(学号)→(学院)→(学院电话)

正确做法:
fanshi_6

1.6 什么是触发器

触发器是一种特殊类型的存储过程,它由事件触发,而不是程序调用或手工启动,当数据 库有特殊的操作时,这些操作由数据库中的事件来触发,自动完成这些SQL语句。使用触发 器可以用来保证数据的有效性和完整性,完成比约束更复杂的数据约束。

简单俩说:也就是对某一个表的一定的操作,触发某种条件,从而执行的一段程序。触发器是一个特殊的存储过程。

实例:
假如有这么两张表:

Create Table Student(              --学生表 
StudentID int primary key, --学号
....
)

Create Table BorrowRecord( --学生借书记录表
BorrowRecord int identity(1,1), --流水号
StudentID int , --学号
BorrowDate datetime, --借出时间
ReturnDAte Datetime, --归还时间
...
)

功能有:
1.如果我更改了学生的学号,我希望他的借书记录仍然与这个学生相关(也就是同时更改借书记录表的学号);
2.如果该学生已经毕业,我希望删除他的学号的同时,也删除它的借书记录。

这时候可以用到触发器。对于1,创建一个Update触发器:

Create Trigger truStudent 
On Student --在Student表中创建触发器
for Update --为什么事件触发
As --事件触发后所要做的事情
if Update(StudentID)
begin

Update BorrowRecord
Set StudentID=i.StudentID
From BorrowRecord br , Deleted d ,Inserted i --Deleted和Inserted临时表
Where br.StudentID=d.StudentID
end

理解触发器里面的两个临时的表:Deleted , Inserted 。注意Deleted 与Inserted分别表示触发事件的表“旧的一条记录”和“新的一条记录”。
一个数据库系统中有两个虚拟表用于存储在表中记录改动的信息,分别是:

操作时 虚拟表Inserted 拟表Deleted
在表记录新增时 存放新增的记录 不存储记录
修改时 存放用来更新的新记录 存放更新前的记录
删除时 不存储记录 存放被删除的记录

对于2,创建一个Delete触发器:

Create trigger trdStudent 
On Student
for Delete
As
Delete BorrowRecord
From BorrowRecord br , Delted d
Where br.StudentID=d.StudentID

1.7 索引是什么?有什么作用以及优缺点?

参考理解:MySQL索引背后的数据结构及算法原理

什么是索引【Index】

(1)是一种快速查询表中内容的机制,类似于新华字典的目录
(2)运用在表中某个些字段上,但存储时,独立于表之外

底层数据结构是什么,为什么使用这种数据结构?

(1)底层数据结构是B+树:
MySQL就普遍使用B+Tree实现其索引结构。在数据结构中,我们最为常见的搜索结构就是二叉搜索树和AVL树(高度平衡的二叉搜索树,为了提高二叉搜索树的效率,减少树的平均搜索长度)了。然而,无论二叉搜索树还是AVL树,当数据量比较大时,都会由于树的深度过大而造成I/O读写过于频繁,进而导致查询效率低下,因此对于索引而言,多叉树结构成为不二选择。特别地,B-Tree的各种操作能使B树保持较低的高度,从而保证高效的查找效率。

2)使用B+树的原因:
查找速度快、效率高,在查找的过程中,每次都能抛弃掉一部分节点,减少遍历个数。(画出什么是B+树)

索引的分类?

唯一索引:唯一索引不允许两行具有相同的索引值
主键索引:为表定义一个主键将自动创建主键索引,主键索引是唯一索引的特殊类型。主键索引要求主键中的每个值是唯一的,并且不能为空
聚集索引(Clustered):表中各行的物理顺序与键值的逻辑(索引)顺序相同,每个表只能有一个
非聚集索引(Non-clustered):非聚集索引指定表的逻辑顺序。数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针。可以有多个,小于249个

索引的优缺点?

(1)优点:
1.大大加快数据的检索速度,这也是创建索引的最主要的原因;
2.加速表和表之间的连接;
3.在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间;
4.通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性;

(2)缺点:
1.时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度;
2.空间方面:索引需要占物理空间。

什么样的字段适合创建索引?

  • 经常作查询SELECT的字段
  • 经常作表连接的字段
  • 经常出现在order by, group by, distinct 后面的字段

创建索引时需要注意什么?

非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值;

取值离散大的字段:(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高;

索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高。

1.8什么是视图?以及视图的使用场景有哪些?

视图是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增,改,查,操作,试图通常是有一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易,相比多表查询。

如下两种场景一般会使用到视图:

  1. 不希望访问者获取整个表的信息,只暴露部分字段给访问者,所以就建一个虚表,就是视图。
  2. 查询的数据来源于不同的表,而查询者希望以统一的方式查询,这样也可以建立一个视图,把多个表查询结果联合起来,查询者只需要直接从视图中获取数据,不必考虑数据来源于不同表所带来的差异。

注意:这个视图是在数据库中创建的 而不是用代码创建的。

简单示例:
为之前的学生和成绩表两表创建视图

create view stu_and_cou 
as
select student.id,student.name,courses.cname
from student,courses
where student.id = courses.id ;

使用视图:
select * from stu_and_cou;
sql_view

1.9 union和union all有什么区别

union在进行表求并集后会去掉重复的元素,所以会对所产生的结果集进行排序运算,删除重复的记录再返回结果。

union all则只是简单地将两个结果集合并后就返回结果。因此,如果返回的两个结果集中 有重复的数据,那么返回的结果集就会包含重复的数据。

1.10 数据库的乐观锁和悲观锁是什么?

参考文章:深入理解乐观锁与悲观锁

数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。

乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
1.在查询完数据的时候就把事务锁起来,直到提交事务
2.实现方式:使用数据库中的锁机制
这样在并发期间一旦有一个事务持有了数据库记录的锁,其他的线程将不能再对数据进行更新了,这就是悲观锁的实现方式。

场景:商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。

MySQL InnoDB中使用悲观锁:

注:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,当你执行一个更新操作后,MySQL会立刻将结果进行提交。set autocommit=0;

> //0.开始事务
>
> begin;/begin work;/start transaction; (三者选一就可以)
>
> //1.查询出商品信息
>
> select status from t_goods where id=1 for update;
>
> //2.根据商品信息生成订单
>
> insert into t_orders (id,goods_id) values (null,1);
>
> //3.修改商品status为2
>
> update t_goods set status=2;
>
> //4.提交事务
>
> commit;/commit work;

普通查询不一样的是,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。

优点与不足:

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
1.在修改数据的时候把事务锁起来,通过version的方式来进行锁定
2.实现方式:使用version版本或者时间戳

数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。

使用版本号实现乐观锁:
使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是不是该数据的最新的版本号。

//1.查询出商品信息
select (status,status,version) from t_goods where id=#{id}
//2.根据商品信息生成订单
//3.修改商品status为2
update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};

优点与不足:
乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。

1.11 超键、候选键、主键、外键分别是什么?

超键:在关系中能唯一标识元组(表每行)的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。

候选键(候选码):是最小超键,即没有冗余元素的超键。

主键(主码):主键就是候选键里面的一个,数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。

外键:在一个表中存在的另一个表的主键称此表的外键。

示例:
假设有如下两个表:

学生(学号,姓名,性别,身份证号,教师编号)
教师(教师编号,姓名,工资)

超健:学生表中含有学号或者身份证号的任意组合都为此表的超键。如:(学号)、(学号,姓名)、(身份证号,性别)等。
候选键:学生表中的候选键为:(学号)、(身份证号)。
主键: 主键就是候选键里面的一个,学生表中,通常会让“学号”做主键,教师表中让“教师编号”做主键。
外键:学生表中的外键就是“教师编号”。

1.12 SQL 约束有哪几种?

NOT NULL:用于控制字段的内容一定不能为空(NULL)。
UNIQUE:控件字段内容不能重复,一个表允许有多个 Unique 约束。
PRIMARY KEY:也是用于控件字段内容不能重复,但它在一个表只允许出现一个。
FOREIGN KEY:用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
*CHECK: *用于控制字段的值范围。

1.13 MyIASM和Innodb两种引擎所使用的索引的数据结构是什么?

答案:都是B+树!

MyIASM引擎,B+树的数据结构中存储的内容实际上是实际数据的地址值。也就是说它的索引和实际数据是分开的,只不过使用索引指向了实际数据。这种索引的模式被称为非聚集索引。

Innodb引擎的索引的数据结构也是B+树,只不过数据结构中存储的都是实际的数据,这种索引有被称为聚集索

1.14 varchar和char的区别

char是一种固定长度的类型,varchar是一种可变长度的类型。
例如:
定义一个char[10]和varchar[10],如果存进去的是 ‘test’,那么char所占的长度依然为10,除了字符 ‘test’ 外,后面跟六个空格,varchar就立马把长度变为4了,取数据的时候,char类型的要用trim()去掉多余的空格,而varchar是不需要的。

char的存取速度还是要比varchar要快得多,因为其长度固定,方便程序的存储于查找。

char的存储方式是:对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节。

varchar的存储方式是:对每个英文字符占用2个字节,汉字也占用2个字节。

两者的存储数据都非unicode的字符数据。

1.15 如何优化MySQL

① 怎么发现有问题的SQL?(通过MySQL慢查询日志对有效率问题的SQL进行监控)

MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10s以上的语句。
query_slow
通过MySQL的慢查询日志,我们可以查询出执行的次数多占用的时间长的SQL、可以通过pt_query_disgest(一种mysql慢日志分析工具)分析Rows examine(MySQL执行器需要检查的行数)项去找出IO大的SQL以及发现未命中索引的SQL,对于这些SQL,都是我们优化的对象。

② 通过explain查询和分析SQL的执行计划:
使用 EXPLAIN 关键字可以知道MySQL是如何处理你的SQL语句的,以便分析查询语句或是表结构的性能瓶颈。通过explain命令可以得到表的读取顺序、数据读取操作的操作类型、哪些索引可以使用、哪些索引被实际使用、表之间的引用以及每张表有多少行被优化器查询等问题。当扩展列extra出现Using filesort和Using temporay,则往往表示SQL需要优化了。

③ SQL语句的优化:

⒈优化insert语句:一次插入多值;

⒉应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描;

⒊应尽量避免在 where 子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描;

⒋优化嵌套查询:子查询可以被更有效率的连接(Join)替代;

⒌很多时候用 exists 代替 in 是一个好的选择。

⒍选择最有效率的表名顺序:数据库的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表将被最先处理

在FROM子句中包含多个表的情况下:

  • 如果三个表是完全无关系的话,将记录和列名最少的表,写在最后,然后依次类推
  • 也就是说:选择记录条数最少的表放在最后

如果有3个以上的表连接查询:

  • 如果三个表是有关系的话,将引用最多的表,放在最后,然后依次类推。
  • 也就是说:被其他表所引用的表放在最后

⒎用IN代替OR:

select * from emp where sal = 1500 or sal = 3000 or sal = 800;
select * from emp where sal in (1500,3000,800);

⒏SELECT子句中避免使用*号:

我们最开始接触 SQL 的时候,“*” 号是可以获取表中全部的字段数据的,但是它要通过查询数据字典完成,这意味着将消耗更多的时间,而且使用 “*” 号写出来的 SQL 语句也不够直观。