1.存储引擎(面试很爱考)
1.1什么是存储引擎?其作用是?
存储引擎实质上就是一个表的存储/组织数据的方式,不同的存储引擎,表储存数据的方式不同
存储引擎是MySQL中特有的术语,在其他数据库中是没有的额(Oracle中有该功能,但是不叫存储引擎)
1.2如何给表添加存储引擎?
在建表的时候,可以在最后小括号的")"的右边使用添加存储引擎:
ENGINE来指定存储引擎。
CHARSET来指定这张表的字符编码方式。
create table t_students(no int primary key,name varchar(255)) engine=InnoDB CHARSET=utf8
通过show create table t_student;可以看到建表的完整信息:
CREATE TABLE `t_student` (
`no` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`cno` int(11) DEFAULT NULL,
PRIMARY KEY (`no`),
KEY `cno` (`cno`),
CONSTRAINT `t_student_ibfk_1` FOREIGN KEY (`cno`) REFERENCES `t_class` (`classno`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8
从上面可以得到一个结论:
mysql的默认引擎是:InnoDB
mysql的默认字符编码方式是:utf8
1.3如何查看mysql中支持哪些存储引擎?
查看当前版本:select version();
查看当前引擎支持信息:show engines \G
总共有9中存储引擎,其中这个版本只支持8种(看support信息),且InnoDB是默认存储引擎
*************************** 1. row ***************************
Engine: FEDERATED
Support: NO
Comment: Federated MySQL storage engine
Transactions: NULL
XA: NULL
Savepoints: NULL
*************************** 2. row ***************************
Engine: MRG_MYISAM
Support: YES
Comment: Collection of identical MyISAM tables
Transactions: NO
XA: NO
Savepoints: NO
*************************** 3. row ***************************
Engine: MyISAM
Support: YES
Comment: MyISAM storage engine
Transactions: NO
XA: NO
Savepoints: NO
*************************** 4. row ***************************
Engine: BLACKHOLE
Support: YES
Comment: /dev/null storage engine (anything you write to it disappears)
Transactions: NO
XA: NO
Savepoints: NO
*************************** 5. row ***************************
Engine: CSV
Support: YES
Comment: CSV storage engine
Transactions: NO
XA: NO
Savepoints: NO
*************************** 6. row ***************************
Engine: MEMORY
Support: YES
Comment: Hash based, stored in memory, useful for temporary tables
Transactions: NO
XA: NO
Savepoints: NO
*************************** 7. row ***************************
Engine: ARCHIVE
Support: YES
Comment: Archive storage engine
Transactions: NO
XA: NO
Savepoints: NO
*************************** 8. row ***************************
Engine: InnoDB
Support: DEFAULT
Comment: Supports transactions, row-level locking, and foreign keys
Transactions: YES
XA: YES
Savepoints: YES
*************************** 9. row ***************************
Engine: PERFORMANCE_SCHEMA
Support: YES
Comment: Performance Schema
Transactions: NO
XA: NO
Savepoints: NO
9 rows in set (0.01 sec)
1.4常见的存储引擎
MyISAM存储引擎
优点:格式、数据、索引都是不同的文件,因此可以被转换为压缩文件,和只读方式来节省空间。数据库空间不足,数据量又大,可以用。
缺点:不支持事物,安全性低
它管理的表具有以下特征:
使用三个文件表示每个表:
格式文件 — 存储表结构的定义(mytable.frm)
数据文件 — 存储表行的内容(mytable.MYD)
索引文件 — 存储表上索引(mytable.MYI):索引是一本书的目录,缩小扫描范围,提高查询效率的一种机制。
可被转换为压缩、只读表来节省空间
对于一张表来说,只要是主键或者加有unique约束的字段上会自动创建索引
InnoDB存储引擎
是默认的存储引擎,也是很重要的一个存储引擎
优点:InnoDB支持事物,以保证数据安全是他最大的特点。支持数据库崩溃之后自动恢复,其安全性很高
缺点:效率不高,也不能压缩,不能转换为只读,不能很好的节省存储空间
它管理的表具有以下特征:
– 每数据和索引存在表空间的逻辑名称中, InnoDB 表空间 tablespace 被用于存储表的内容(表空间是一个逻辑名称。表空间存储数据+索引。)
– 每个 InnoDB 表在数据库目录中以.frm 格式文件表示
– 提供一组用来记录事务性活动的日志文件
– 用 COMMIT(提交)、SAVEPOINT 及ROLLBACK(回滚)支持事务处理
– 提供全 ACID 兼容
– 在 MySQL 服务器崩溃后提供自动恢复
– 多版本(MVCC)和行级锁定
– 支持外键及引用的完整性,包括级联删除和更新
MEMORY存储引擎
优点:查询效率最高,不需要和硬盘进行交互
缺点:不安全,关机之后数据消失,因为数据和索引都是在内存当中
为什么放在内存上效率高?
因为内存是直接取,是电流的速度,光速,从硬盘上取,硬盘是机械行为,硬盘里有磁针,再快也没有电流速度快
事物(要求必须掌握,理解和精通)
2.1什么是事物?
一个事物其实就是一个完整的业务逻辑,是最小的工作单元,也就是要完成一件事
什么是完整的业务逻辑?
假设转账从A转账到B,1000元
将A 账户减去1000(update语句)
将B账户减去1000(update语句)
这就是一个完整的业务逻辑
以上操作是一个最小的工作单元,要么同时成功,要么同时失败,不可再分
上面两个update语句要求必须同时成功和失败,这样才能保证钱是正确的
2.2 只有MDL语句才会有事物这一说,其它语句与事物无关
insert
delete
update
只有上面三个语句是对数据库中的数据进行增删改的,只要操作,一旦涉及到数据的增删改,那么就一定要考虑数据安全问题,数据安全第一位!!
2.3如果所有的事用一条MDL语句即可完成那么事务没有存在的价值
2.4从本质上来说,就是多条DML语句同时成功或者同时失败
2.5 事物是怎么做到多条MDL语句同时成功和同时失败的?
InnoDB存储引擎提供一组用来记录事物性活动的日志文件
事务开启了:
insert
insert
insert
delete
update
update
update
事务结束了!
在事物的执行过程中我们可以提交事物,也可以回滚事物
提交事物
提交事务就是将数据全部彻底持久化到数据库表中,并且清空事务性活动的日志文件。
提交事务标志着事物的结束,并且是全部成功的结束
回滚事物
将之前所有的DML操作全部撤销,并且清空全部活动的日志文件
回滚事物标志着事物的结束,并且是全部失败的结束
2.3 怎么提交和回滚事物?
提交事务 :commit
回滚事物:rollback,永远只能回滚到上一次的提交点
事物对应的英语单词数是:transaction
测试一下,在mysql中默认的事物行为是怎样的?
默认情况下,是支持自动提交事物的,每执行一条DML语句就提交一次
如何将自动提交语句关闭?
start transaction
insert into dept_bak(deptno,dname,loc) values (10,‘sales’,‘ddd’);
查的时候是有数据的,但是因为没有提交,因此,可以回滚后恢复原样
mysql> select* from dept_bak;
+--------+------------+----------+
| DEPTNO | DNAME | LOC |
+--------+------------+----------+
| 10 | ACCOUNTING | NEW YORK |
| 20 | RESEARCH | DALLAS |
| 30 | SALES | CHICAGO |
| 40 | OPERATIONS | BOSTON |
| 10 | ACCOUNTING | NEW YORK |
| 20 | RESEARCH | DALLAS |
| 30 | SALES | CHICAGO |
| 40 | OPERATIONS | BOSTON |
| 10 | sales | ddd |
+--------+------------+----------+
rollback;
mysql> select * from dept_bak;
+--------+------------+----------+
| DEPTNO | DNAME | LOC |
+--------+------------+----------+
| 10 | ACCOUNTING | NEW YORK |
| 20 | RESEARCH | DALLAS |
| 30 | SALES | CHICAGO |
| 40 | OPERATIONS | BOSTON |
| 10 | ACCOUNTING | NEW YORK |
| 20 | RESEARCH | DALLAS |
| 30 | SALES | CHICAGO |
| 40 | OPERATIONS | BOSTON |
+--------+------------+----------+
自动提交实际上是不符合开发习惯的,因为一个业务通常需要多条DML语句同时执行才能完成,为了保证数据的安全,必须要同时成功之后再提交,所以不能执行一条就提交一条
提交事物完整流程
start transaction;
insert into dept_bak(deptno,dname,loc) values (10,‘sales’,‘ddd’);
commit;
mysql> select * from dept_bak;
+--------+------------+----------+
| DEPTNO | DNAME | LOC |
+--------+------------+----------+
| 10 | ACCOUNTING | NEW YORK |
| 20 | RESEARCH | DALLAS |
| 30 | SALES | CHICAGO |
| 40 | OPERATIONS | BOSTON |
| 10 | ACCOUNTING | NEW YORK |
| 20 | RESEARCH | DALLAS |
| 30 | SALES | CHICAGO |
| 40 | OPERATIONS | BOSTON |
| 10 | sales | ddd |
+--------+------------+----------+
回滚事务流程
start transaction;
insert into dept_bak(deptno,dname,loc) values (10,‘sales’,‘ddd’);
rollback;
2.4 事务的4个特性(会考)
原子性(A):事务是最小的工作单元,不可再分
一致性( C ):在一个事务中,所有操作必须保证同时成功或者同时失败,以保证数据的一致性
隔离性(I):A事务和B事务之间具有一定的隔离,教室A和教室B之间有一道墙,A事务在操作一张表时,另一个事务B也操作这张表会怎样?
多线程并发访问一张表一样,会带来安全问题
持久性(D):事务最终结果的一个保障,事务提交,就相当于将没有保存到硬盘上的数据保存到硬盘上
2.5事务的隔离性!
A教室和B教室之间有一道墙,这道墙可以很厚也可以很薄,这就是事务的隔离级别,这道墙越厚表示隔离级别越高。
2.5.1事务和事务之间的隔离级别有哪些?
读未提交:read and uncommitted(最低隔离)
读已提交:read and committed
可重复读:repeatable read
序列化读/串行化:serializable(最高隔离)
2.5.2读未提交(没有提交就读到了)
事务A可以读取到事务B未提交的数据
相当于在A教室说话B教室就马上能听到
缺点:存在脏读现象!我们称读到了脏数据
这种隔离级别一般都是理论上的实际上没人用,大多数都是二档起步
2.5.3 读已提交(提交之后才能读到)
事务A只能读取到事务B已经提交之后的数据
这种隔离级别解决了脏读的现象
缺点:不可重复读取数据,在事务A开启之后,第一次读到的数据是3条,当前事务还没有结束,可能第二次在读取的时候读到的数据是4条,3不等于4 ,称为不可重复读取,也就是读两次前后不一致。
这种隔离级别是真实的数据,每一个读到的数据绝对的真实,oracle数据库默认的隔离级别是:read commited
2.5.4可重复读取(提交之后也读不到,永远读取的都是开启事务时的数据)
事务A开启之后,不管是多久,每一次在事务A 从事务B中读到的数据都是一致的,及时事务B已经将数据修改,并且提交了,事务A读到的数据还是没有改变,这就是可重复读,其解决了不可重复读问题
缺点:可能会出现幻影,每一次读取到的数据都是幻象,不够真实
2.5.5序列化,最低高隔离级别
事务A进行的时候,事务B不能操作,等A完成了才能继续操作,每一次读取到的数据都是最真实的,并且效率是最低的
如果用该隔离级别算银行总账的时候,就谁也不能存款谁也不能取款
表示事务排队,不能并发
缺点:效率最低,但是他解决了所有的问题
2.6事务隔离级别验证
查看当前隔离级别
mysql> SELECT @@tx_isolation(设置好要退一下,再进来exit)
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
被测试的表t_user
验证:read uncommited
mysql> set global transaction isolation level read uncommitted;
事务A | 事务B |
---|---|
use test | use test |
start transaction; | start transaction; |
insert into t_user(name) values(‘zhangsan’);(这一步还没有提交) | / |
/ | select * from t_user;(这一步就可以把还没有提交的内容查出来了) |
查出来的内容
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
+----------+
验证:read commited
mysql> set global transaction isolation level read committed;
事务A | 事务B |
---|---|
use test | use test |
start transaction; | start transaction; |
select * from t_user(2条记录) | |
/ | insert into t_user(name) values(‘zhangsan’);(这一步还没有提交) |
select * from t_user(还是查到两条记录) | / |
/ | commit; |
select * from t_user(此时能够查到3条记录了) | / |
验证:repeatable read
mysql> set global transaction isolation level repeatable read;
事务A | 事务B |
---|---|
use test | use test |
start transaction; | start transaction; |
select * from t_user(3条记录——初始状态) | |
/ | insert into t_user(name) values('assss); |
/ | commit; |
select * from t_user(此时还是只能查到3条记录) | / |
验证:serializable
mysql> set global transaction isolation level serializable;
事务A | 事务B |
---|---|
use test | use test |
start transaction; | start transaction; |
select * from t_user(4条记录) | / |
insert into t_user values(‘1111’); | |
/ | /select * from t_user(4条记录)此时命令提交成功了,但是结果不显示 |
commit | /; |
/ | 左边结束事物,立马显示结果 |
3.索引
3.1什么是索引?(面试重点)
索引是在数据库的字段上添加的,为了提高查询效率的添加机制,一张表的一个字段可以添加索引,多个字段联合起来也可以添加索引,索引相当于一本书的目录,是为了缩小扫描范围而存在的一种机制。
对于一本字典来说,查找某个汉字的方式:
一页一页找直到找到为止,这种查找方式属于全字典扫描,效率较低
第二种,先通过目录定位大概位置,然后直接定位到该位置,进行局域 的扫描,缩小扫描范围,进行快速查找,这种扫描方式通过索引检索,效率较高。
对于表t_user来说
id name(nameIndex)
1 jack
2 ppp
3 ttt
4 uuu
select * from t_user where name=‘jack’;
以上的查询语句回到name字段上扫描,因为条件是:name=jack
如果name字段上没有添加索引的话,他会将name字段上的每一个值都去比对一遍,进行全扫描,效率较低
查询主要有两种方式,一种是全扫描,一种是用索引(index)
目录需要排序,汉语字典前面的目录也是排序的,只有排序了才会有区间查找一说
索引需要进行排序,并且排序和Treeset(TreeMap)数据结构相同,他是一个自平衡的二叉树,索引在Mysql中是一个B—Tree数据结构,遵循做小右大原则存放,采取中序遍历方式取数据
面试问:索引的底层逻辑是什么?
是B-tree,B-TRee的底层逻辑又是二叉树
3.2索引实现原理(面试会问!去看看专业的讲解)
2.2.1在所有数据库中主键上都会自动添加索引,mysql中一个字段上如果有unique约束时也会自动创建索引
2.2.2在任何数据库中,任何一张表的任何一条记录在硬盘上都有物理存储编号
2.2.3在mysql中索引是一个单独的对象,不同的存储引擎中以不同的形式存在,在myisam中存储在一个.MYI文件中,在InooDB中,存储索引存储在tableplace 的逻辑名称中,不管索引存储在哪里,都是以一个树的形式存在。
假设有一张用户表:t_user
id(PK) name 每一行记录在硬盘上都有物理存储编号
----------------------------------------------------------------------------------
100 zhangsan 0x1111
120 lisi 0x2222
99 wangwu 0x8888
88 zhaoliu 0x9999
101 jack 0x6666
55 lucy 0x5555
130 tom
大致实现原理
3.3主键上及unique字段上都会自动添加索引,在mysql中
什么条件下会考虑添加索引?
条件一:数据量庞大(到底多大算庞大?因为每一个硬件条件不同)
条件二:该字段经常出现在where后面,以条件形式存在,也就是说该字段经常被扫描
条件三:该字段有很少的DML操作,因为DML之后索引需要重新排序
建议不要随便添加索引,因为索引需要维护,太多的话会影响系统性能。建议通过主键查询,通过unique约束的字段进行查询,效率较高
3.4索引的创建和删除语法
create index 表名_字段名_index on 表名(字段名);
create index emp_ename_index on emp(ename);
drop index 表名_字段名_index on 表名;
drop index emp_ename_index on emp;
3.5怎么看一个SQL语句是否用了索引进行检索
explain select * from emp where ename=‘KING’;
j检查这条语句是否用了索引
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | emp | ALL | NULL | NULL | NULL | NULL | 14 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
type =all,表示全扫描
+----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-------------+
| 1 | SIMPLE | emp | ref | emp_ename_index | emp_ename_index | 33 | const | 1 | Using where |
+----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-------------+
type=ref,表示他用了索引
索引有实效的时候,什么时候失效?(面试必问,尽量全部说)
情况一:
select * from emp where ename like '@T";
ename 上即使添加了索引也不会走,因为模糊匹配中以‘%‘’号开头了,所以尽量避免模糊查询时用百分号开始,这是一种优化策略。
情况二:
使用or的时候会失效,使用or要求两边都要有索引才会用索引,如果只有一边有索引,那么另外一边也不会用索引,可以使用union,他不会让索引失效
情况三:
使用复合索引的时候,没有使用左侧的列查找失效
复合索引:两个字段或者多个字段联合起来添加一个索引。
create index emp_ename_job_index on emp(ename,job)
select * from emp where ename = 'jack";
用索引了
select * from emp where job = 'sales";
索引失效
情况四:
在where中索引参加了运算
select * from emp where sal = 800;(走索引)
select * from emp where sal +1=800;(不走索引)
情况五:
在where中索引列使用了函数
explain select * from emp where lower(ename)=‘simith’;
索引是各种数据库进行优化的手段,是优化时优先考虑的因素。
索引的分类
单一索引:一个字段上添加索引
复合索引:两个字段,或者多个字段添加索引
主键索引:主键上添加索引
唯一性索引:具有unique约束的字段上添加索引
注意:唯一性比较弱的字段上添加索引用处不大,越唯一效率越高
4.视图
view:站在不同的角度去看待同一份数据
4.1 怎么创建试图对象?怎么删除
先快速复制一张表
create table dept2 as select * from dept;
创建视图,注意只有DQL语句才能创建视图
create view dept2_view as select * from dept2;
删除视图
drop view dept2_view
4.2 用视图干什么?,起到一个区域化操作的用处
可以先对数据进行查找,然后包装成视图,然后单独对试图进行操作
可以面向视图对象进行增删改查,对视图对象的增删改查会导致原表被操作
insert into dept2_view(deptno,dname,loc) values (60,'sales','beijig')
4.3 视图对象在实际开发中到底有什么用?
视图是用来简化sql语句的,将很长的,需要经常重复的sql语句当所一个视图创建出来,相当于引用指向很长的sql语句,以后动代码的时候也不用更改每一个sql语句,只需要修改视图对象所映射的SQL语句即可。
面向试图开发时,可以像使用table一样,视图是存储在硬盘上的,不会消失
增删改查内部一般都叫做(CRUD)
create 增
delete 删
update 改
retrive(查:检索)
5.DBA常用命令
5.1 创建新用户
create user 用户名 identified by '密码‘;
create user heweibin identified by ‘123344’;
5.2 数据的导入导出(重点掌握)
在windows 的dos窗口,也就是一开始的那个窗口进行导入导出操作
5.2.1 数据的导出
(1)导出database
C:\Users\15187>mysqldump test>D:\ test.sql -uroot -p123456
(2)导出单个表
C:\Users\15187>mysqldump test emp>D:\ emp.sql -uroot -p123456
5.2.2 数据导入
注意:这里需要先登录到mysql数据库中才能导入
第一步:创建新的datebase
create database test;
第二步:使用数据库
use test;
第三步:初始化数据库:source
mysql>source D:\ test.sql
6.数据库设计三范式(关系数据库基础,ER图)
6.1什么是数据库设计三范式?
数据库表的设计依据,教你如何进行数据库表的设计
6.2 数据库设计范式有三个(经常问,熟记在心)
6.2.1 第一范式:数据库任何一张表必须有主键,每一个字段原子性不可分
6.2.2 第二范式:建立在第一范式之上,要求所有非主键字段完全依赖主键,不要产生部分依赖
6.2.3 建立在第二范式基础之上,要求所有非主键字段直接依赖主键,不要产生传递依赖
6.2.4 BC范式和第四范式?????
设计数据库表的时候按照以上的范式进行可以避免表中数据冗余以及空间浪费
6.3第一范式
第一范式是最核心,最重要的范式,所有的表都有主键,并且每个·字段都是原子不可再分
学生编号 学生姓名 联系方式
------------------------------------------
1001 张三 zs@gmail.com,1359999999
1002 李四 ls@gmail.com,13699999999
1001 王五 ww@163.net,13488888888
上表不符合第一范式:
没有主键,且联系方式是可以再分的
学生编号(pk) 学生姓名 邮箱地址 联系电话
----------------------------------------------------
1001 张三 zs@gmail.com 1359999999
1002 李四 ls@gmail.com 13699999999
1003 王五 ww@163.net 13488888888
6.4 第二范式
建立在第一范式的基础之上,要求所有非主键字段必须完全依赖主键,不要产生部分依赖。
学生编号 学生姓名 教师编号 教师姓名
----------------------------------------------------
1001 张三 001 王老师
1002 李四 002 赵老师
1003 王五 001 王老师
1001 张三 002 赵老师
分析以上表是否符合第一范式?
不符合,没有主键
可以用学生编号和教师编号联合起来做主键,那符合第二范式吗?
一个学生对应多个老师,一个老师对应多个学生,是多对多的关系
不满足,张三依赖的是1001,王老师依赖的是001,显然产生了部分依赖。
产生部分依赖缺点:空间冗余,空间浪费。
为了让以上表满足第二范式,使用三张表来表示多对多的关系,一张学生表,一张是学生教室关系表
学生编号(pk) 学生姓名
------------------
教师编号 (pk) 教师姓名
------------------
id 学生编号 (FK)) 教师编号(FK)
--------------------------------------------------------------
背口诀:多对多,三张表,关系表两外键(背会)
6.5第三范式
第三范式建立在第二范式基础之上,要求所有非主键字段直接依赖主键,不要产生传递依赖
学生编号(PK) 学生姓名 班级编号 班级名称
---------------------------------------------------------
1001 张三 01 一年一班
1002 李四 02 一年二班
1003 王五 03 一年三班
1004 赵六 03 一年三班
以上关系,满足第二范式吗?
满足。因为上表的主键是学生编号,主键不是复合主键,没有产生部分依赖。班级名称依赖于学生编号。但是一个教室有多个学生,是一对多的关系。
以上是否满足第三范式?
不满足:一年一班依赖01,01依赖1001,产生了传递依赖
怎么办?
分为两张表
班级表:一
班级编号(pk) 班级名称
----------------------------------------
01 一年一班
02 一年二班
03 一年三班
学生表:多
学生编号(PK) 学生姓名 班级编号(fk)
-------------------------------------------
1001 张三 01
1002 李四 02
1003 王五 03
1004 赵六 03
背口诀:一对多,两张表,多的表加外键。
6.6总结表设计(背出来)
一对多?
多对多?
一对一?
一对一,实际开发中可能存在数据太大,这时候要拆分表
一对一怎么设计?使用外键唯一性约束
没有拆分表之前:一张表
t_user
id login_name login_pwd real_name email address........
---------------------------------------------------------------------------
1 zhangsan 123 张三 zhangsan@xxx
2 lisi 123 李四 lisi@xxx
...
这种长度的表建议拆分为两张,一张登录表,一张用户信息表
t_login 登录信息表
id(pk) login_name login_pwd
---------------------------------
1 zhangsan 123
2 lisi 123
t_user 用户详细信息表
id(pk) real_name email address........ login_id(fk+unique)
-----------------------------------------------------------------------------------------
100 张三 zhangsan@xxx 1
200 李四 lisi@xxx 2
加一个login_id(fk+unique)使用外键加唯一性约束
数据库设计三范式是理论上的实践和理论有偏差,有的时候会拿冗余换执行速度,因为表和表连接次数越多,效率越低,最终目的还是为了满足客户需求。
面试的时候问,你对数据库三范式有了解吗?就回答三范式定义加上面的话