数据库阶段面试题

文章目录

数据库阶段面试题

前述

  • 什么是数据库?
    所谓的数据库就是指存储和管理数据的仓库
  • 什么是关系型数据库?
    底层以二维表的形式保存数据的库就是关系型数据库
  • 常见的关系型数据库有哪些?(了解)
    Sql Server:微软,收费;Oracle:甲骨文公司提供,收费;Oracle:甲骨文公司提供,收费;DB2:IBM公司提供,收费;Sqlite:迷你数据库,嵌入式设备中(安卓、苹果手机、pad)

1.MySQL和Oracle的区别(知晓几个重点区别就行)

1.1宏观上:

  1. Oracle是大型的数据库而Mysql是中小型数据库;Mysql是开源的,Oracle是收费的,且价格昂贵。
  2. Oracle支持大并发,大访问量,是OLTP的最好的工具。
  3. 安装占用的内存也是有差别,Mysql安装完成之后占用的内存远远小于Oracle所占用的内存,并且Oracle越用所占内存也会变多。

1.2微观上:

  1. 对于事务的支持
    Mysql对于事务默认是不支持的,只是有某些存储引擎中如:innodb可以支持;而Oracle对于事物是完全支持的。
  2. 并发性

什么是并发性?并发性是OLTP(On-Line Transaction Processing联机事务处理过程)数据库最重要的特性,并发性涉及到资源的获取、共享与锁定。

  • Mysql以表锁为主,对资源锁定的力度很大,如果一个session对一个表加锁时间过长,会让其他session无法更新此表的数据。
  • Oracle使用行级锁,对资源锁定的力度要小很多,只是锁定sql需要的资源,并且加锁是在数据库中的数据行上,不依赖于索引。所以oracle对并发性的支持要好很多。
  1. 数据的持久性
    Oracle保证提交的事务均可以恢复,因为Oracle把提交的sql操作线写入了在线联机日志文件中,保存到磁盘上,如果出现数据库或者主机异常重启,重启Oracle可以靠联机在线日志恢复客户提交的数据。
    Mysql默认提交sql语句,但是如果更新过程中出现db或者主机重启的问题,也可能会丢失数据。
  2. 事务隔离级别
    MySQL是repeatable read的隔离级别,而Oracle是read commited的隔离级别,同时二者都支持serializable串行化事务隔离级别,可以实现最高级别的。
    读一致性。每个session提交后其他session才能看到提交的更改。Oracle通过在undo表空间中构造多版本数据块来实现读一致性,每个session 查询时,如果对应的数据块发生变化,Oracle会在undo表空间中为这个session构造它查询时的旧的数据块。
    MySQL没有类似Oracle的构造多版本数据块的机制,只支持read commited的隔离级别。一个session读取数据时,其他session不能更改数据,但可以在表最后插入数据。session更新数据时,要加上排它锁,其他session无法访问数据
  3. 提交方式
    MySQL默认是自动提交,而Oracle默认不自动提交,需要用户手动提交,需要在写commit;指令或者点击commit按钮
  4. 逻辑备份
    Mysql逻辑备份是要锁定数据,才能保证备份的数据是一致的,影响业务正常的DML(数据操纵语言Data Manipulation Language)使用;Oracle逻辑备份时不锁定数据,且备份的数据是一致的。
  5. sql语句的灵活性
    mysql对sql语句有很多非常实用而方便的扩展,比如limit功能(分页),insert可以一次插入多行数据;Oracle在这方面感觉更加稳重传统一些,Oracle的分页是通过伪列和子查询完成的,插入数据只能一行行的插入数据。
  6. 数据复制
    MySQL:复制服务器配置简单,但主库出问题时,丛库有可能丢失一定的数据。且需要手工切换丛库到主库。
    Oracle:既有推或拉式的传统数据复制,也有dataguard的双机或多机容灾机制,主库出现问题是,可以自动切换备库到主库,但配置管理较复杂。
  7. 分区表和分区索引
    MySQL的分区表还不太成熟稳定;Oracle的分区表和分区索引功能很成熟,可以提高用户访问db的体验。
  8. 售后与费用
    Oracle是收费的,出问题找客服;Mysql是免费的的,开源的,出问题自己解决。
  9. 权限与安全
    Oracle的权限与安全概念比较传统,中规中矩;MySQL的用户与主机有关,感觉没有什么意义,另外更容易被仿冒主机及ip有可乘之机。
  10. 性能诊断方面
    Oracle有各种成熟的性能诊断调优工具,能实现很多自动分析、诊断功能。比如awr、addm、sqltrace、tkproof等 ;MySQL的诊断调优方法较少,主要有慢查询日志。

2.数据库的三大范式、五大约束

2.1 三大范式

为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。

在实际开发中最为常见的设计范式有三个:

  1. 第一范式(确保每列保持原子性)
    第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。

    第一范式的合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式,如下表所示。
    在这里插入图片描述
    上表所示的用户信息遵循了第一范式的要求,这样在对用户使用城市进行分类的时候就非常方便,也提高了数据库的性能。

  2. 第二范式(确保表中的每列都和主键相关)
    第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。

    比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如下表所示。
    订单信息表:
    在这里插入图片描述
    这样就产生一个问题:这个表中是以订单编号和商品编号作为联合主键。这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品编号相关。所以在这里违反了第二范式的设计原则。

    而如果把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了。如下所示。
    在这里插入图片描述这样设计,在很大程度上减小了数据库的冗余。如果要获取订单的商品信息,使用商品编号到商品信息表中查询即可。

    扫描二维码关注公众号,回复: 11967990 查看本文章
  3. 第三范式(确保每列都和主键列直接相关,而不是间接相关)

    第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

    比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系。而不可以在订单表中添加关于客户其它信息(比如姓名、所属公司等)的字段。如下面这两个表所示的设计就是一个满足第三范式的数据库表。
    在这里插入图片描述
    这样在查询订单信息的时候,就可以使用客户编号来引用客户信息表中的记录,也不必在订单信息表中多次输入客户信息的内容,减小了数据冗余。

2.2五大约束

字段约束/列约束 --> 约束: 限制

  1. 主键约束
    主键约束:如果为一个列添加了主键约束,那么这个列就是主键,主键的特点是唯一且不能为空
    主键的作用: 作为一个唯一标识,唯一的表示一条表记录(作用类似于人的身份证号,可以唯一的表示一个人一样。)
    添加主键约束,例如将id设置为主键:
create table stu(
	id int primary key,
	...
);

如果主键是数值类型,为了方便插入主键(并且保证插入数据时,主键不会因为重复而报错),可以设置一个主键自增策略。
主键自增策略是指:设置了自增策略的主键,可以在插入记录时,不给id赋值,只需要设置一个null值,数据库会自动为id分配一个值(AUTO_INCREMENT变量,默认从1开始,后面依次+1),这样既可以保证id是唯一的,也省去了设置id的麻烦。
将id主键设置为自增:

create table stu(
	id int primary key auto_increment,
	...
);
alter table stu add constraint pk_xs primary key (id)
  1. 非空约束
    非空约束:如果为一个列添加了非空约束,那么这个列的值就不能为空,但可以重复。
    添加非空约束,例如为password添加非空约束:
create table user(
	password varchar(50) not null,
	...
);
  1. 唯一约束
    唯一约束:如果为一个列添加了唯一约束,那么这个列的值就必须是唯一的(即不能重复),但可以为空
    添加唯一约束,例如为username添加唯一约束及非空约束:
create table user(
	username varchar(50) unique not null,
	...
);
  1. 外键约束
    外键其实就是用于通知数据库两张表数据之间对应关系的这样一个列
    这样数据库就会帮我们维护两张表中数据之间的关系。

创建表的同时添加外键:

create table emp(
	id int,
	name varchar(50), 
	dept_id int,
	foreign key(dept_id) references dept(id)
);

在这里插入图片描述

  • 如果是要表示两张表的数据之间存在对应关系,只需要在其中的一张表中添加一个列,保存另外一张表的主键,就可以保存两张表数据之间的关系。

    但是添加的这个列(dept_id)对于数据库来说就是一个普通列,数据不会知道两张表存在任何关系,因此数据库也不会帮我们维护这层关系。
    在这里插入图片描述

  • 如果将deptid列设置为外键,等同于通知数据库,部门表和员工表之间存在对应关系,deptid列中的数据要参考部门的主键,数据库一旦知道部门和员工表之间存在关系,就会帮我们维护这层关系型。

  1. 默认约束
create table student(
	sex char(2) default '男'
);

3.左外连接和右外连接的区别

  • 左外连接查询:可以将左边表中的所有记录都查询出来,右边表只显示和左边相对应的数据,如果左边表中某些记录在右边没有对应的数据,右边显示为null即可。
select * From dept left join emp on emp.dept_id=dept.id; 

在这里插入图片描述

  • 右外连接查询:可以将右边表中的所有记录都查询出来,左边表只显示和右边相对应的数据,如果右边表中某些记录在左边没有对应的数据,可以显示为null。
select * From dept right join emp on emp.dept_id=dept.id;

在这里插入图片描述

扩展:如果想将两张表中的所有数据都查询出来(左外+右外并去除重复记录),可以使用全外连接查询,但是mysql又不支持全外连接查询。
可以使用union将左外连接查询的结果和右外连接查询的结果合并在一起,并去除重复的记录。例如
select * From dept left join emp on emp.dept_id=dept.id union select * From dept right join emp on emp.dept_id=dept.id;
需要注意的是:union可以将两条SQL语句执行的结果合并有前提:
(1)两条SQL语句查询的结果列数必须一致
(2)两条SQL语句查询的结果列名、顺序也必须一致
并且union默认就会将两个查询中重复的记录去除(如果不希望去除重复记录,可以使用union all)
在这里插入图片描述

4.数据库的事务,4大特性,隔离级别,传播特性

4.1 事务(Database Transaction)

简单理解: 事务就是将一堆的SQL语句绑定在一起执行, 这堆SQL要么全部执行成功, 要么全部执行失败!
转账: A和B账户: 1000 A转账给B 100元 acc(银行账户表)

-- 开启事务
-- A账户减去100元:
update acc set money=money-100 where name='A';
-- B账户加上100元:
update acc set money=money+100 where name='B';
-- 提交事务

4.2.4 事务的四大特性 (ACID)

  • 原子性: 事务中的所有操作(SQL语句)是一个整体, 不可再分割, 要么全执行成功, 要么全执行失败!
  • 一致性: 事务前后的业务数据总和是不变的(比如, 转账之前A和B的账户金额总和是2000, 转账之后, 不管转账成功与否, A和B的账户总金额一定也是2000.)
  • 隔离性: 不同的事务之间是有隔离的, 事务1和事务2是完全隔离开来的, 事务1不能看到事务2正在进行中的操作。
    事务1查看A和B的总金额, 事务2正在进行A转账给B100元
    如果隔离等级非常高,事务1是看不到事务2正在进行中的操作, 反之, 如果隔离等级较低,就可能会出现事务1会看到事务2正在进行的操作。
  • 持久性:(数据库也是以文件的形式保存数据,将数据以文件形式保存到硬盘上就称之为持久保存)
    一旦事务提交, 对数据的修改将会持久的保存到硬盘上, 除非再次修改, 否则数据将不再变化, 这称之为持久性。

4.2.5 事务并发读问题

多个事务对相同的数据同时进行操作,这叫做事务并发。
在事务并发时,如果没有采取必要的隔离措施,可能会导致各种并发问题,破坏数据的完整性等。这些问题中,其中有三类是读问题,分别是:脏读、不可重复读、幻读。

  1. 脏读(dirty read):读到另一个事务的未提交更新数据,即读取到了脏数据;
    例如:A给B转账100元但未提交事务,在B查询后,A做了回滚操作,那么B查询到了A未提交的数据,就称之为脏读。
  2. 不可重复读(unrepeatable read):对同一记录的两次读取不一致,因为另一事务对该记录做了修改(是针对修改操作
    例如:在事务1中,前后两次查询A账户的金额,在两次查询之间,另一事务2对A账户的金额做了修改,此种情况可能会导致事务1中,前后两次查询的结果不一致。这就是不可重复度
  3. 幻读(虚读)(phantom read):对同一张表的两次查询不一致,因为另一事务插入了一条记录(是针对插入或删除操作);

注意:mysql默认的是不允许出现脏读和不可重复读,所以在下面演示之前需要设置mysql允许出现脏读、不可重复读等。

set tx_isolation='read-uncommitted';

脏读示例

-- 在窗口1中,开启事务,执行A给B转账100元
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
update acc set money=money-100 where name='A';
update acc set money=money+100 where name='B';

-- 在窗口2中,开启事务,查询B的账户金额
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
select * from acc where name='B'; -- 出现脏数据
-- 切换到窗口1,回滚事务,撤销转账操作。
rollback; -- 回滚事务

-- 切换到窗口2,查询B的账户金额
select * from acc where name='B';

在窗口2中,B看到自己的账户增加了100元(此时的数据A操作事务并未提交),此种情况称之为"脏读"。

不可重复读示例

-- 在窗口1中,开启事务,查询A账户的金额
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
select * from acc where name='A';

-- 在窗口2中,开启事务,查询A的账户金额减100
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
update acc set money=money-100 where name='A'; -- A账户减去100
select * from acc where name='A';
commit; -- 提交事务

-- 切换到窗口1,再次查询A账户的金额。
select * from acc where name='A'; -- 前后查询结果不一致

在窗口1中,前后两次对同一数据(账户A的金额)查询结果不一致,是因为在两次查询之间,另一事务对A账户的金额做了修改。此种情况就是"不可以重复读"

幻读示例

-- 在窗口1中,开启事务,查询账户表中是否存在id=3的账户
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
select * from acc where id=3;

-- 在窗口2中,开启事务,往账户表中插入了一条id为3记录,并提交事务。
-- 设置mysql允许出现脏读、不可重复度、幻读
set tx_isolation='read-uncommitted';
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
insert into acc values(3, 'C', 1000);
commit; -- 提交事务

-- 切换到窗口1,由于上面窗口1中查询到没有id为3的记录,所以可以插入id为3的记录。
insert into acc values(3, 'C', 1000); -- 插入会失败!

在窗口1中,查询了不存在id为3的记录,所以接下来要执行插入id为3的记录,但是还未执行插入时,另一事务中插入了id为3的记录并提交了事务,所以接下来窗口1中执行插入操作会失败。
探究原因,发现账户表中又有了id为3的记录(感觉像是出现了幻觉)。这种情况称之为"幻读"

以上就是在事务并发时常见的三种并发读问题,那么如何防止这些问题的产生?
可以通过设置事务隔离级别进行预防。

4.2.6 事务隔离级别

事务隔离级别分四个等级,在相同数据环境下,对数据执行相同的操作,设置不同的隔离级别,可能导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力也是不同的。
set tx_isolation=‘read-uncommitted’;

  1. READ UNCOMMITTED(读未提交数据)
    安全级别最低, 可能出现任何事务并发问题(比如脏读、不可以重复读、幻读等)
    性能最好(不使用!!)
  2. READ COMMITTED(读已提交数据)(Oracle默认)
    防止脏读,没有处理不可重复读,也没有处理幻读;
    性能比REPEATABLE READ好
  3. REPEATABLE READ(可重复读)(MySQL默认)
    防止脏读和不可重复读,不能处理幻读问题;
    性能比SERIALIZABLE好
  4. SERIALIZABLE(串行化)
    不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;
    性能最差;
    MySQL的默认隔离级别为REPEATABLE READ,即可以防止脏读和不可重复读

设置隔离级别

  1. MySQL查询当前的事务隔离级别
    select @@tx_isolation;

  2. MySQL设置事务隔离级别(了解)
    (1) set tx_isolation=‘read-uncommitted’; 
    安全性最差,容易出现脏读、不可重复读、幻觉读,但性能最高
    (2) set tx_isolation=‘read-committed’;
    安全性一般,可防止脏读,但容易出现不可重复读、幻觉读
    (3) set tx_isolation=‘repeatable-read’;
    安全性较好,可防止脏读、不可重复读,但是容易出现幻读
    (4) set tx_isolation=‘serialiable’;
    安全性最好,可以防止一切事务并发问题,但是性能最差。

  3. JDBC设置事务隔离界别
    JDBC中通过Connection提供的方法设置事务隔离级别:
    Connection. setTransactionIsolation(int level)
    参数可选值如下:
    Connection.TRANSACTION_READ_UNCOMMITTED 1(读未提交数据)
    Connection.TRANSACTION_READ_COMMITTED 2(读已提交数据)
    Connection.TRANSACTION_REPEATABLE_READ 4(可重复读)
    Connection.TRANSACTION_SERIALIZABLE 8(串行化)
    Connection.TRANSACTION_NONE 0(不使用事务)
    提示:在开发中,一般情况下不需要修改事务隔离级别

  4. JDBC中实现转账例子
    提示:JDBC中默认是自动提交事务,所以需要关闭自动提交,改为手动提交事务
    也就是说, 关闭了自动提交后, 事务就自动开启, 但是执行完后需要手动提交或者回滚!!
    (1)执行下面的程序,程序执行没有异常,转账成功!A账户减去100元,B账户增加100元。
    (2)将第4步、5步中间的代码放开,再次执行程序,在转账过程中抛异常,转账失败!由于事务回滚,所以A和B账户金额不变。

public static void main(String[] args) throws SQLException {
    
    
	Connection conn = null;
	Statement stat = null;
	ResultSet rs = null;
	try {
    
    
		//1.获取连接
		conn = JDBCUtil.getConn();
		//2.关闭JDBC自动提交事务(默认开启事务)
		conn.setAutoCommit(false);  
		//3.获取传输器
		stat = conn.createStatement();
		
		/* ***** A给B转账100元 ***** */
		//4.A账户减去100元
		String sql = "update acc set money=money-100 where name='A'";
		stat.executeUpdate(sql);
		
		//int i = 1/0; // 让程序抛出异常,中断转账操作
		
		//5.B账户加上100元
		sql = "update acc set money=money+100 where name='B'";
		stat.executeUpdate(sql);
		
		//6.手动提交事务
		conn.commit();
		System.out.println("转账成功!提交事务...");
	} catch (Exception e) {
    
    
		e.printStackTrace();
		//一旦其中一个操作出错都将回滚,使两个操作都不成功 
		conn.rollback();    
		System.out.println("执行失败!回滚事务...");
	} finally{
    
    
		JDBCUtil.close(conn, stat, rs);
	}
}

4.2.7 事务的传播特性

指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

一共有七种传播行为:

  1. PROPAGATION_REQUIRED 如果当前方法存在一个事务,则将该方法置于同一个事务中,如果之前不存在事务,则另新开启一个事务(delete ,insert update)

  2. PROPAGATION_SUPPORTS 如果当前方法存在一个事务,则将该方法置于同一个事务中,如果之前不存在事务,则进行非事务执行(select)

  3. PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

  4. PROPAGATION_REQUIRES_NEW 使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作为事务管理器。
    它会开启一个新的事务。如果一个事务已经存在,则先将这个存在的事务挂起。

  5. PROPAGATION_NOT_SUPPORTED PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。

  6. PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常。

7.PROPAGATION_NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

5.什么是存储过程,有啥优缺点

存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合;
调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。

  • 优点:
    1、重复使用:存储过程可以重复使用,从而可以减少数据库开发人员的工作量。
    2、减少网络流量:存储过程位于服务器上,调用的时候只需要传递存储过程的名称以及参数就可以了,因此降低了网络传输的数据量。
    3、安全性:参数化的存储过程可以防止SQL注入式攻击,而且可以将Grant、Deny以及Revoke权限应用于存储过程。
  • 缺点:
    1、更改比较繁琐:如果更改范围大到需要对输入存储过程的参数进行更改,或者要更改由其返回的数据,则仍需要更新程序集中的代码以添加参数、更新 GetValue() 调用,等等,这时候估计比较繁琐。
    2、可移植性差:由于存储过程将应用程序绑定到 SQL Server,因此使用存储过程封装业务逻辑将限制应用程序的可移植性。如果应用程序的可移植性在您的环境中非常重要,则需要将业务逻辑封装在不特定于 RDBMS 的中间层中。

6.什么是触发器

6.1触发器的定义

触发器(TRIGGER)是由事件来触发某个操作。这些事件包括INSERT语句、UPDATE语句和DELETE语句。当数据库系统执行这些事件时,会激活促发其执行相应的操作。

6.2创建与使用触发器

/* 触发器 */ ------------------
    触发程序是与表有关的命名数据库对象,当该表出现特定事件时,将激活该对象
    监听:记录的增加、修改、删除。
-- 创建触发器
CREATE TRIGGER trigger_name trigger_time trigger_event ON tbl_name FOR EACH ROW trigger_stmt
    参数:
    trigger_time是触发程序的动作时间。它可以是 before 或 after,以指明触发程序是在激活它的语句之前或之后触发。
    trigger_event指明了激活触发程序的语句的类型
        INSERT:将新行插入表时激活触发程序
        UPDATE:更改某一行时激活触发程序
        DELETE:从表中删除某一行时激活触发程序
    tbl_name:监听的表,必须是永久性的表,不能将触发程序与TEMPORARY表或视图关联起来。
    trigger_stmt:当触发程序激活时执行的语句。执行多个语句,可使用BEGIN...END复合语句结构
-- 删除
DROP TRIGGER [schema_name.]trigger_name
可以使用old和new代替旧的和新的数据
    更新操作,更新前是old,更新后是new.
    删除操作,只有old.
    增加操作,只有new.
-- 注意
    1. 对于具有相同触发程序动作时间和事件的给定表,不能有两个触发程序。
-- 字符连接函数
concat(str1,str2,...])
concat_ws(separator,str1,str2,...)
-- 分支语句
if 条件 then
    执行语句
elseif 条件 then
    执行语句
else
    执行语句
end if;
-- 修改最外层语句结束符
delimiter 自定义结束符号
    SQL语句
自定义结束符号
delimiter ;     -- 修改回原来的分号
-- 语句块包裹
begin
    语句块
end
-- 特殊的执行
1. 只要添加记录,就会触发程序。
2. Insert into on duplicate key update 语法会触发:
    如果没有重复记录,会触发 before insert, after insert;
    如果有重复记录并更新,会触发 before insert, before update, after update;
    如果有重复记录但是没有发生更新,则触发 before insert, before update
3. Replace 语法 如果有记录,则执行 before insert, before delete, after delete, after insert

弊端:增加程序的复杂度,有些业务逻辑在代码中处理,有些业务逻辑用触发器处理,会使后期维护变得困难;避免使用触发器,除非绝对必要。

7.索引的原理,有啥优缺点,什么时候适合/不适合建索引,索引失效的场景,常见的索引类型(如主键索引,聚簇索引,非聚簇索引等)

7.1 什么是索引?

索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B树, B+树和Hash。
索引的作用就相当于目录的作用。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。

哈希索引:
对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。

7.2 索引的优缺点

索引的优点

**可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。毕竟大部分系统的读请求总是大于写请求的。 ** 另外,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

索引的缺点

  1. 创建索引和维护索引需要耗费许多时间:当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低SQL执行效率。
  2. 占用物理存储空间 :索引需要使用物理文件存储,也会耗费一定空间。

7.3 索引的创建原则–什么时候适合/不适合建索引

  1. 经常需要用来做过滤条件的列,可以考虑创建索引。不会出现在where条件中的字段不该建立索引。
  2. 频繁改动的列,不适合创建索引。因为索引是需要实现维护好的,如果频繁改动索引的列,实际上是改动了索引的节点,需要重新进行维护。在维护索引期间,sql的io性能会降低。
  3. 不被经常查询的字段没有必要建立索引
  4. 尽可能的考虑建立联合索引而不是单列索引
    因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。

7.4 索引失效的场景

  1. 模糊查询时,%放在前面,索引会失效。
SELECT COUNT(*) FROM emp WHERE NAME LIKE '%雷';
  1. 使用or进行过滤时,or前后的字段都应该建立索引,否则索引失效。
SELECT COUNT(*) FROM emp WHERE NAME LIKE '齐%' OR job ='CEO';
SELECT COUNT(*) FROM emp WHERE NAME LIKE '齐%' OR id =16;--索引生效,因为id是主键,自动维护了一个索引
  1. 在索引列上进行了运算,导致索引失效。id是主键
EXPLAIN SELECT * FROM emp WHERE  id-1 = "15";
  1. 索引列发生了数据的隐式转换,导致索引失效。Name这个列是varchar类型的,直接传入一个123,导致数据类型发生了改变。
EXPLAIN SELECT * FROM emp WHERE  NAME = 123;

在这里插入图片描述
5. 进行非空判断的时候,索引也会失效

EXPLAIN SELECT * FROM emp WHERE  job IS NOT NULL;

在这里插入图片描述
事实证明,is null也造成了索引失效

EXPLAIN SELECT * FROM emp WHERE  job IS NULL;

在这里插入图片描述
在这里插入图片描述

7.5 常见的索引类型

7.5.1主键索引(Primary Key)

数据表的主键列使用的就是主键索引。
一张数据表有只能有一个主键,并且主键不能为null,不能重复。
在mysql的InnoDB的表中,当没有显示的指定表的主键时,InnoDB会自动先检查表中是否有唯一索引的字段,如果有,则选择该字段为默认的主键,否则InnoDB将会自动创建一个6Byte的自增主键。

7.5.2 二级索引(辅助索引)

二级索引又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。
唯一索引,普通索引,前缀索引等索引属于二级索引。
PS:不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。

  1. 唯一索引(Unique Key) :唯一索引也是一种约束。**唯一索引的属性列不能出现重复的数据,但是允许数据为NULL,一张表允许创建多个唯一索引。**建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
  2. 普通索引(Index)普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和NULL。
  3. 前缀索引(Prefix) :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,
    因为只取前几个字符。
  4. 全文索引(Full Text) :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6之前只有MYISAM引擎支持全文索引,5.6之后InnoDB也支持了全文索引。
    二级索引:
    在这里插入图片描述

7.5.3 聚集索引与非聚集索引

7.5.3.1聚集索引

聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。
在 Mysql 中,InnoDB引擎的表的 .ibd文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
聚集索引的优点
聚集索引的查询速度非常的快,因为整个B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。
聚集索引的缺点

  1. 依赖于有序的数据 :因为B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或UUID这种又长又难比较的数据,插入或查找的速度肯定比较慢。
  2. 更新代价大 : 如果对索引列的数据被修改时,那么对应的索引也将会被修改,
    而且况聚集索引的叶子节点还存放着数据,修改代价肯定是较大的,
    所以对于主键索引来说,主键一般都是不可被修改的。
7.5.3.2非聚集索引

非聚集索引即索引结构和数据分开存放的索引。
二级索引属于非聚集索引。

MYISAM引擎的表的.MYI文件包含了表的索引,
该表的索引(B+树)的每个叶子非叶子节点存储索引,
叶子节点存储索引和索引对应数据的指针,指向.MYD文件的数据。

非聚集索引的叶子节点并不一定存放数据的指针,
因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。

非聚集索引的优点
更新代价比聚集索引要小 。非聚集索引的更新代价就没有聚集索引那么大了,非聚集索引的叶子节点是不存放数据的
非聚集索引的缺点

  1. 跟聚集索引一样,非聚集索引也依赖于有序的数据
  2. 可能会二次查询(回表) :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。

8.MySQL数据库主从复制,读写分离,分库分表的原理

参考文章

8.1主从复制原理

  • 主从复制:MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点

MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。

  • MySQL主从复制的类型
    基于语句的复制(默认):在主服务器上执行的语句,从服务器执行同样的语句
    基于行的复制:把改变的内容复制到从服务器
    混合类型的复制:一旦发现基于语句无法精确复制时,就会采用基于行的复制

  • 用途

  1. 做数据的热备,作为后备数据库,主数据库服务器故障后,可切换到从数据库继续工作,避免数据丢失。
  2. 架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
  3. 读写分离,使数据库能支撑更大的并发。在业务复杂的系统中,有这么一个情景,有一句sql语句需要锁表,导致暂时不能使用读的服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作
  • 原理
    在这里插入图片描述
    (1)master服务器将数据的改变记录二进制binlog日志,当master上的数据发生改变时,则将其改变写入二进制日志中;
    (2)slave服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开启一个I/OThread去master服务器中读取更新的二进制日志文件;
    (3)同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制日志文件,并保存至从节点本地的中继日志中,从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后I/OThread和SQLThread将进入睡眠状态,等待下一次被唤醒。
也就是说:
从库会生成两个线程,一个I/O线程,一个SQL线程;
I/O线程会去请求主库的binlog,并将得到的binlog写到本地的relay-log(中继日志)文件中;
主库会生成一个log dump线程,用来给从库I/O线程传binlog;
SQL线程,会读取relay log文件中的日志,并解析成sql语句逐一执行;

8.2 读写分离原理

  • 原理:让主数据库(master)处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库(slave)处理SELECT查询操作。读写分离要建立在主从复制的基础上;
  • 为什么要读写分离呢?
    因为数据库的“写”(写10000条数据到oracle可能要3分钟)操作是比较耗时的。但是数据库的“读”(从oracle读10000条数据可能只要5秒钟)很快。所以读写分离,解决的是,数据库的写入影响了查询的效率。

8.3 分库分表的原理

参考文章:MySQL大表优化方案

8.3.1影响数据库性能的因素

  • 数据量
    MySQL单库数据量在5000万以内性能比较好,超过阈值后性能会随着数据量的增大而变弱。MySQL单表的数据量是500w-1000w之间性能比较好,超过1000w性能也会下降。
  • 磁盘
    因为单个服务的磁盘空间是有限制的,如果并发压力下,所有的请求都访问同一个节点,肯定会对磁盘IO造成非常大的影响。
  • 数据库连接
    数据库连接是非常稀少的资源,如果一个库里既有用户、商品、订单相关的数据,当海量用户同时操作时,数据库连接就很可能成为瓶颈。

8.3.2 垂直拆分 or 水平拆分?

当我们单个库太大时,我们先要看一下是因为表太多还是数据量太大,如果是表太多,则应该将部分表进行迁移(可以按业务区分),这就是所谓的垂直拆分。如果是数据量太大,则需要将表拆成更多的小表,来减少单表的数据量,这就是所谓的水平拆分

8.3.2.1垂直拆分

  • 垂直分库
    垂直分库针对的是一个系统中的不同业务进行拆分,比如用户一个库,商品一个库,订单一个库。 一个购物网站对外提供服务时,会同时对用户、商品、订单表进行操作。没拆分之前, 全部都是落到单一的库上的,这会让数据库的单库处理能力成为瓶颈。如果垂直分库后还是将用户、商品、订单放到同一个服务器上,只是分到了不同的库,这样虽然会减少单库的压力,但是随着用户量增大,这会让整个数据库的处理能力成为瓶颈,还有单个服务器的磁盘空间、内存也会受非常大的影响。 所以我们要将其拆分到多个服务器上,这样上面的问题都解决了,以后也不会面对单机资源问题。

  • 垂直分表
    也就是“大表拆小表”,基于列字段进行的。一般是表中的字段较多,将不常用的, 数据较大,长度较长(比如text类型字段)的拆分到“扩展表“。一般是针对那种几百列的大表,也避免查询时,数据量太大造成的“跨页”问题。
    在这里插入图片描述

8.3.2.2 水平拆分

  • 水平分表
    和垂直分表有一点类似,不过垂直分表是基于列的,而水平分表是基于全表的。水平拆分可以大大减少单表数据量,提升查询效率。
    在这里插入图片描述

  • 水平分库分表
    将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。

8.3.2.3 几种常用的分库分表的策略

  • HASH取模
    假设有用户表user,将其分成3个表user0,user1,user2.路由规则是对3取模,当uid=1时,对应到的是user1,uid=2时,对应的是user2.
  • 范围分片
    从1-10000一个表,10001-20000一个表。
  • 地理位置分片
    华南区一个表,华北一个表。
  • 时间分片
    按月分片,按季度分片等等,可以做到冷热数据。

9.数据库的冷备份和热备份

9.1 冷备份

冷备份发生在数据库已经正常关闭的情况下,当正常关闭时会提供给我们一个完整的数据库。冷备份时将要害性文件拷贝到另外的位置的一种说法。对于备份Oracle信息而言,冷备份时最快和最安全的方法。

  • 冷备份的优点是:
  1. 是非常快速的备份方法(只需拷贝文件)
  2. 容易归档(简单拷贝即可)
  3. 容易恢复到某个时间点上(只需将文件再拷贝回去)
  4. 能与归档方法相结合,作数据库“最新状态”的恢复。
  5. 低度维护,高度安全。
  • 冷备份的缺点:
  1. 单独使用时,只能提供到“某一时间点上”的恢复。
  2. 在实施备份的全过程中,数据库必须要作备份而不能作其它工作。也就是说,在冷备份过程中,数据库必须是关闭状态。
  3. 若磁盘空间有限,只能拷贝到磁带等其它外部存储设备上,速度会很慢。
  4. 不能按表或按用户恢复。
    值得注意的是冷备份必须在数据库关闭的情况下进行,当数据库处于打开状态时,执行数据库文件系统备份是无效的 。而且在恢复后一定要把数据库文件的属组和属主改为mysql。

9.1 热备份

热备份是在数据库运行的情况下,备份数据库操作的sql语句,当数据库发生问题时,可以重新执行一遍备份的sql语句。

  • 热备份的优点:
  1. 可在表空间或数据文件级备份,备份时间短。
  2. 备份时数据库仍可使用。
  3. 可达到秒级恢复(恢复到某一时间点上)。
  4. 可对几乎所有数据库实体作恢复。
  5. 恢复是快速的,在大多数情况下在数据库仍工作时恢复。
  • 热备份的缺点:
  1. 不能出错,否则后果严重。
  2. 若热备份不成功,所得结果不可用于时间点的恢复。
  3. 因难于维护,所以要特别仔细小心,不允许“以失败而告终”。

10.数据库的优化

10.1 选取最适用的字段属性

MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快。因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小

例如,在定义邮政编码这个字段时,如果将其设置为CHAR(255),显然给数据库增加了不必要的空间,甚至使用VARCHAR这种类型也是多余的,因为CHAR(6)就可以很好的完成任务了。同样的,如果可以的话,我们应该使用MEDIUMINT而不是BIGIN来定义整型字段。

另外一个提高效率的方法是在可能的情况下,应该尽量把字段设置为NOTNULL,这样在将来执行查询的时候,数据库不用去比较NULL值。
对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为ENUM类型。因为在MySQL中,ENUM类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。这样,我们又可以提高数据库的性能。

10.2使用连接(JOIN)来代替子查询(Sub-Queries)

MySQL从4.1开始支持SQL的子查询。这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。例如,我们要将客户基本信息表中没有任何订单的客户删除掉,就可以利用子查询先从销售信息表中将所有发出订单的客户ID取出来,然后将结果传递给主查询,如下所示:

DELETE FROM customerinfo
WHERE CustomerID NOT IN (SELECT CustomerID FROM salesinfo)

使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询可以被更有效率的连接(JOIN)…替代。例如,假设我们要将所有没有订单记录的用户取出来,可以用下面这个查询完成:

SELECT * FROM customerinfo
WHERE CustomerID NOT IN (SELECTC CustomerID FROM salesinfo)

如果使用连接(JOIN)…来完成这个查询工作,速度将会快很多。尤其是当salesinfo表中对CustomerID建有索引的话,性能将会更好,查询如下:

SELECT * FROM customerinfo
LEFT JOIN salesinfo ON customerinfo.CustomerID=salesinfo.CustomerID
WHERE salesinfo.CustomerID ISNULL

连接(JOIN)…之所以更有效率一些,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作。

10.3 使用联合(UNION)来代替手动创建的临时表

MySQL从4.0的版本开始支持union查询,它可以把需要使用临时表的两条或更多的select查询合并的一个查询中。在客户端的查询会话结束的时候,临时表会被自动删除,从而保证数据库整齐、高效。使用union来创建查询的时候,我们只需要用UNION作为关键字把多个select语句连接起来就可以了,要注意的是所有select语句中的字段数目要相同。下面的例子就演示了一个使用UNION的查询。

SELECT Name,Phone FROM client UNION
SELECT Name,BirthDate FROM author UNION
SELECT Name,Supplier FROM product

10.4 事务

尽管我们可以使用子查询(Sub-Queries)、连接(JOIN)和联合(UNION)来创建各种各样的查询,但不是所有的数据库操作都可以只用一条或少数几条SQL语句就可以完成的。更多的时候是需要用到一系列的语句来完成某种工作。但是在这种情况下,当这个语句块中的某一条语句运行出错的时候,整个语句块的操作就会变得不确定起来。设想一下,要把某个数据同时插入两个相关联的表中,可能会出现这样的情况:第一个表中成功更新后,数据库突然出现意外状况,造成第二个表中的操作没有完成,这样,就会造成数据的不完整,甚至会破坏数据库中的数据。要避免这种情况,就应该使用事务,它的作用是:要么语句块中每条语句都操作成功,要么都失败。换句话说,就是可以保持数据库中数据的一致性和完整性。事物以BEGIN关键字开始,COMMIT关键字结束。在这之间的一条SQL操作失败,那么,ROLLBACK命令就可以把数据库恢复到BEGIN开始之前的状态。

BEGIN; 
INSERT INTO salesinfo SET CustomerID=14; 
UPDATE inventory SET Quantity=11 WHERE item='book'; 
COMMIT;

事务的另一个重要作用是当多个用户同时使用相同的数据源时,它可以利用锁定数据库的方法来为用户提供一种安全的访问方式,这样可以保证用户的操作不被其它的用户所干扰。

10.5 锁定表

尽管事务是维护数据库完整性的一个非常好的方法,但却因为它的独占性,有时会影响数据库的性能,尤其是在很大的应用系统中。由于在事务执行的过程中,数据库将会被锁定,因此其它的用户请求只能暂时等待直到该事务结束。如果一个数据库系统只有少数几个用户来使用,事务造成的影响不会成为一个太大的问题;但假设有成千上万的用户同时访问一个数据库系统,例如访问一个电子商务网站,就会产生比较严重的响应延迟。

其实,有些情况下我们可以通过锁定表的方法来获得更好的性能。下面的例子就用锁定表的方法来完成前面一个例子中事务的功能。

LOCK TABLE inventory WRITE SELECT Quantity FROM inventory WHERE Item='book';
...
UPDATE inventory SET Quantity=11 WHERE Item='book'; UNLOCKTABLES

这里,我们用一个select语句取出初始数据,通过一些计算,用update语句将新值更新到表中。包含有WRITE关键字的LOCKTABLE语句可以保证在UNLOCKTABLES命令被执行之前,不会有其它的访问来对inventory进行插入、更新或者删除的操作。

10.6 使用外键

锁定表的方法可以维护数据的完整性,但是它却不能保证数据的关联性。这个时候我们就可以使用外键。

例如,外键可以保证每一条销售记录都指向某一个存在的客户。在这里,外键可以把customerinfo表中的CustomerID映射到salesinfo表中CustomerID,任何一条没有合法CustomerID的记录都不会被更新或插入到salesinfo中。

CREATE TABLE customerinfo( 
	CustomerID INT NOT NULL,
	PRIMARYKEY(CustomerID)
)TYPE=INNODB;

CREATE TABLE salesinfo( 
	SalesID INT NOT NULL,
	CustomerID INT NOT NULL,
	PRIMARYKEY(CustomerID,SalesID),
	FOREIGNKEY(CustomerID) REFERENCES customerinfo(CustomerID) ON DELETE CASCADE
)TYPE=INNODB;

注意例子中的参数“ON DELETE CASCADE”。该参数保证当customerinfo表中的一条客户记录被删除的时候,salesinfo表中所有与该客户相关的记录也会被自动删除。如果要在MySQL中使用外键,一定要记住在创建表的时候将表的类型定义为事务安全表InnoDB类型。该类型不是MySQL表的默认类型。定义的方法是在CREATE TABLE语句中加上TYPE=INNODB。如例中所示。

10.7 使用索引

索引是提高数据库性能的常用方法,它可以令数据库服务器以比没有索引快得多的速度检索特定的行,尤其是在查询语句当中包含有MAX(),MIN()和ORDER BY这些命令的时候,性能提高更为明显。
那该对哪些字段建立索引呢?

一般说来,索引应建立在那些将用于JOIN,WHERE判断和ORDER BY排序的字段上。尽量不要对数据库中某个含有大量重复的值的字段建立索引。对于一个ENUM类型的字段来说,出现大量重复值是很有可能的情况

例如customerinfo中的“province”…字段,在这样的字段上建立索引将不会有什么帮助;相反,还有可能降低数据库的性能。我们在创建表的时候可以同时创建合适的索引,也可以使用ALTERTABLE或CREATEINDEX在以后创建索引。此外,MySQL从版本3.23.23开始支持全文索引和搜索。全文索引在MySQL中是一个FULLTEXT类型索引,但仅能用于MyISAM类型的表。对于一个大的数据库,将数据装载到一个没有FULLTEXT索引的表中,然后再使用ALTERTABLE或CREATEINDEX创建索引,将是非常快的。但如果将数据装载到一个已经有FULLTEXT索引的表中,执行过程将会非常慢。

10.8优化的查询语句

绝大多数情况下,使用索引可以提高查询的速度,但如果SQL语句使用不恰当的话,索引将无法发挥它应有的作用。
下面是应该注意的几个方面。

  • 首先,最好是在相同类型的字段间进行比较的操作。
    在MySQL3.23版之前,这甚至是一个必须的条件。例如不能将一个建有索引的INT字段和BIGINT字段进行比较;但是作为特殊的情况,在CHAR类型的字段和VARCHAR类型字段的字段大小相同的时候,可以将它们进行比较。

  • 其次,在建有索引的字段上尽量不要使用函数进行操作。
    例如,在一个DATE类型的字段上使用YEAE()函数时,将会使索引不能发挥应有的作用。所以,下面的两个查询虽然返回的结果一样,但后者要比前者快得多。

  • 第三,在搜索字符型字段时,我们有时会使用LIKE关键字和通配符,这种做法虽然简单,但却也是以牺牲系统性能为代价的。
    例如下面的查询将会比较表中的每一条记录。

SELECT    *    FROM    books
WHERE    name    like"MySQL%"

但是如果换用下面的查询,返回的结果一样,但速度就要快上很多:

SELECT    *    FROM    books
WHERE    name>="MySQL"    and name    <"MySQM"

最后,应该注意`避免在查询中让MySQL进行自动类型转换,因为转换过程也会使索引变得不起作用。

11.补充

11.1char(n)、varchar(n)、text都可以表示字符串类型,其区别在于?

  1. char(n)在保存数据时,如果存入的字符串长度小于指定的长度n,后面会用空格补全,因此可能会造成空间浪费,但是char类型的存储速度较varchar和text快
    因此char类型适合存储长度固定的数据,这样就不会有空间浪费,存储效率比后两者还快!
  2. varchar(n)保存数据时,按数据的真实长度存储,剩余的空间可以留给别的数据用,因此varchar不会浪费空间。
    因此varchar适合存储长度不固定的数据,这样不会有空间的浪费
  3. text是大文本类型,一般文本长度超过255个字符,就会使用text类型存储。

11.2常见的表关系

一对多(多对一)、一对一、多对多

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

11.3where和having子句的区别

11.3.1 列出最低薪资大于1500的各种职位,显示职位和该职位最低薪资

  1. 根据职位进行分组, 统计每个职位的最低薪资
select job,min(sal) from emp
group by job;
  1. 筛选出 最低薪资大于1500 的职位有哪些
select job,min(sal) from emp

group by job

having min(sal)>1500;

要统计最低薪资大于1500的职位有哪些, 必须先求出每个职位的最低薪资, 再进行筛选过滤(最低薪资大于1500的职位)

如果要求每个职位的最低薪资, 就要先分组, 所以顺序是, 先分组, 再统计每个职位的最低薪资, 再筛选, 如果是在分组之后进行筛选过滤的话, 不能使用where, 而是要使用having, 两者的区别是:
(1)相同点: where和having都是用于筛选过滤
(2)where是用于在没有分组之前, 对结果进行筛选过滤
(3)having是在分组之后, 对结果进行筛选过滤。
(4)where子句中不能使用列别名和聚合函数(count,sum,max,min,avg)
(5)having子句中可以使用列别名和聚合函数

11.4 添加或删除主键及自增

  1. 创建stu学生表,不添加主键自增, 查看表结果
use mydb1; -- 切换到mydb1库
drop table if exists stu; -- 删除stu学生表(如果存在)
create table stu( -- 重建stu学生表,没有主键自增
    id int,
    name varchar(20),
    gender char(1),
    birthday date
);
desc stu; -- 查看表结构

表结构如下: 没有主键约束和自增。
在这里插入图片描述

  1. 如果表没有创建,或者要删除重建,在创建时可以指定主键或主键自增
drop table if exists stu; -- 删除stu表
create table stu( -- 重新创建stu表时,指定主键自增
    id int primary key auto_increment,
    name varchar(20),
    gender char(1),
    birthday date
);
desc stu; -- 查看表结构

表结构如下: 已经添加了主键约束和自增。
在这里插入图片描述

  1. 如果不想删除重建表,也可以通过修改表添加主键或主键自增
    再次执行第1步,创建stu学生表,不添加主键自增,查看表结果
    – 例如: 将stu学生表中的id设置为主键和自动增长
alter table stu modify id int primary key auto_increment;
desc stu; -- 查看表结构

在这里插入图片描述
如果只添加主键约束,不设置自增

alter table stu modify id int primary key;

如果已经添加主键约束,仅仅设置自增,但需注意:
(1)如果没有设置主键,不可添加自增
(2)只有当主键是数值时,才可以添加自增

alter table stu modify id int auto_increment;
  1. 如果想删除主键自增
-- 删除主键自增时,要先删除自增
alter table stu modify id int;
-- 再删除主键约束
alter table stu drop primary key;
desc stu; -- 查看表结构

在这里插入图片描述

11.5 添加外键约束

11.5.1添加外键方式一:建表时添加外键

现有部门表如下:

-- 创建部门表
create table dept(
	id int primary key auto_increment, -- 部门编号
	name varchar(20) -- 部门名称
);

要求创建员工表,并在员工表中添加外键关联部门主键

-- 创建员工表
create table emp(
    id int primary key auto_increment, -- 员工编号
    name varchar(20), -- 员工姓名
    dept_id int, -- 部门编号
    foreign key(dept_id) references dept(id) -- 指定dept_id为外键
);

11.5.2添加外键方式二:建表后添加外键

现有部门表和员工表:

-- 创建部门表
create table dept(
    id int primary key auto_increment, -- 部门编号
    name varchar(20) -- 部门名称
);
-- 创建员工表
create table emp(
    id int primary key auto_increment, -- 员工编号
    name varchar(20), -- 员工姓名
    dept_id int -- 部门编号
);
-- 如果表已存在,可以使用下面这种方式:
alter table emp add constraint fk_dept_id foreign key(dept_id) references dept(id);

其中 fk_dept_id (名字由自己定义),是指外键约束名称,也可以将【constraint fkdeptid】省略,MySQL会自动分配一个外键名称,将来可以通过该名称删除外键。
foreign key(deptid)中的deptid为外键

添加外键约束(多对多)
– 现有学生(stu)表和教师(tea)表:

-- 创建学生表
create table stu(
    stu_id int primary key auto_increment, -- 学生编号
    name varchar(20) -- 学生姓名
);
-- 创建教师表
create table tea(
	tea_id int primary key auto_increment, -- 教师编号
	name varchar(20) -- 教师姓名
);

– 添加第三方表(stu_tea)表示学生表和教师表关系
– 创建学生和教师关系表

create table stu_tea(
    stu_id int, -- 学生编号
    tea_id int, -- 教师编号
    primary key(stu_id,tea_id), -- 设置联合主键
    foreign key(stu_id) references stu(stu_id), -- 添加外键
    foreign key(tea_id) references tea(tea_id) -- 添加外键
);

其中为了防止重复数据,将stuid和teaid设置为联合主键。
将stuid设置为外键,参考stu表中的stuid列
并将teaid设置为外键,参考tea表中的teaid列

11.6 删除外键约束

  1. 首先通过 “show create table 表名”语法,查询含有外键表的建表语句,例如:
show create table emp;

显示结果如下:
在这里插入图片描述
其中,empibfk1是在创建表时,数据库为外键约束指定的一个名字,删除这个名字即可删除外键关系,例如:

alter table emp drop foreign key emp_ibfk_1;

在这里插入图片描述
外键删除成功!

11.7 SQL语句的书写顺序&SQL语句的执行顺序:

  • SQL语句的书写顺序:
    select * | 列名 – 确定要查询的列有哪些
    from 表名 – 确定查询哪张表
    where 条件 – 通过筛选过滤, 剔除不符合条件的记录
    group by 分组的列 – 指定根据哪一列进行分组
    having 条件 – 通过条件对分组后的数据进行筛选过滤
    order by 排序的列 – 指定根据哪一列进行排序
    limit (countPage-1)*rowCount, rowCount – 指定返回第几页记录以及每页显示多少条

  • SQL语句的执行顺序:
    from 表名 – 确定查询哪张表
    where 条件 – 通过筛选过滤, 剔除不符合条件的记录
    select * | 列名 列别名 – 确定要查询的列有哪些,
    group by 分组的列 – 指定根据哪一列进行分组
    having 条件 – 通过条件对分组后的数据进行筛选过滤
    order by 排序的列 – 指定根据哪一列进行排序
    limit (countPage-1)*rowCount, rowCount

  • 关于where中不能使用列别名但是可以使用表别名?
    是因为, 表别名是声明在from中, from先于where执行, 先声明再使用没有问题, 但是列别名是声明在select中, where先于select执行, 如果先使用列别名, 再声明, 这样执行会报错!!

11.8 SQL注入攻击

11.8.1 什么是SQL注入攻击?

由于后台的SQL语句是拼接而来的。其中的参数是由用户提交的,如果用户在提交参数时,在其中掺杂了一些SQL关键字或者特殊符号,就可能会导致SQL语句的语意发生变化。从而执行一些意外的操作。

模拟用户登陆案例
(1)准备数据
use jt_db;
create table user(
id int primary key auto_increment,
username varchar(50),
password varchar(50)
);
insert into user values(null,‘张三’,‘123’);
insert into user values(null,‘李四’,‘234’);
(2)创建LoginUser 类,提供 main 方法 和 login 方法。
在这里插入图片描述在这里插入图片描述
执行时,输入:
在这里插入图片描述
或输入
在这里插入图片描述

11.8.2如何防止SQL注入攻击?

  • 使用PreparedStatement对象来替代Statement对象。添加loginByPreparedSatement方法,在方法中,使用PreparedStatement来代替Statement作为传输器对象使用!
    在这里插入图片描述再次执行程序,按照上面的操作登录。此时,已经成功的防止了SQL注入攻击问题了。
    使用PreparedStatement对象可以防止SQL注入攻击,而且通过方法设置参数更加的方便且不易出错!还可以从某些方面提高程序执行的效率!
    PreparedStatement对象是如何防止SQL注入攻击的?(原理)
    使用PreparedStatement对象是先将SQL语句的骨架发送给服务器编译,编译之后SQL语句的骨架和语义就不会再被改变了,再将SQL语句中的参数发送给服务器,即使参数中再包含SQL关键字或者特殊符号,也不会导致SQL的骨架或语义被改变,只会被当作普通的文本来处理!

  • 使用正则表达式对用户提交的参数进行校验
    如果参数中有(# – ’ or等)这些符号就直接结束程序,通知用户输入的参数不合法

  • 字符串过滤
    比较通用的一个方法:(||之间的参数可以根据自己程序的需要添加)

public static boolean sql_inj(String str){
    
    
	String inj_str = "'|and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|,";
	String inj_stra[] = split(inj_str,"|");
	for (int i=0 ; i < inj_stra.length ; i++ ){
    
    
		if (str.indexOf(inj_stra[i])>=0){
    
    
			return true;
		}
	}
	return false;
}

11.9 级联更新、级联删除

  • 创建db20库、dept表、emp表并插入记录,删除db20库(如果存在),并重新创建db20库
drop database if exists db20;
create database db20 charset utf8;
use db20;
-- 创建部门表, 要求id, name字段
create table dept(
	id int primary key auto_increment, -- 部门编号
	name varchar(20) -- 部门名称
);
-- 往部门表中插入记录
insert into dept values(null, '财务部');
insert into dept values(null, '人事部');
insert into dept values(null, '科技部');
insert into dept values(null, '销售部');
-- 创建员工表, 要求id, name, dept_id
create table emp(
    id int primary key auto_increment, -- 员工编号
    name varchar(20), -- 员工姓名
    dept_id int, -- 部门编号
    foreign key(dept_id) references dept(id) -- 指定外键
    on update cascade -- 级联更新
    on delete cascade -- 级联删除
);
insert into emp values(null, '张三', 1);
insert into emp values(null, '李四', 2);
insert into emp values(null, '老王', 3);
insert into emp values(null, '赵六', 4);
insert into emp values(null, '刘能', 4);

级联更新:主表(dept表)中的主键发生更新时(例如将销售部的id改为40),从表(emp表)中的记录的外键数据也会跟着该表(即赵六和刘能的部门编号也会更新为40)
级联删除:如果不添加级联删除,当删除部门表中的某一个部门时(例如删除4号部门),若该部门在员工表中有对应的员工(赵六和刘能),删除会失败!
若果添加了级联删除,当删除部门表中的某一个部门时,若该部门在员工表中有对应的员工,会在删除部门的同时,将员工表中对应的员工也删除!

Notice:数据库阶段的面试题不仅要掌握基本概念,还要结合你的实际项目准备,比如你项目中哪些业务用了事务,如何实现的,哪些表字段建了索引,为啥要这么设计等

猜你喜欢

转载自blog.csdn.net/qianzhitu/article/details/108355656
今日推荐