MySQL笔记(三)
一、存储引擎(了解=面试必考)
是什么?有什么用?
存储引擎是MySQL中特有的一个术语?其他数据中没有。(Oracle中有,但不叫这个名字。)存储引擎这个名字高端大气上档次。
存储引擎实际上是一个表存储数据的方式。
不同的存储引擎,表存储数据的方式不同。
怎么给表指定存储引擎
-- 展示建表语句
show create table tbl_student;
我们可以在建表的时候,在最后小括号“)”的右边使用:
ENGINE
:指定存储引擎。
CHARSET
:指定这张表的字符编码方式。
MySQL默认的存储引擎是是 InnoDB
。默认编码方式是 utf8
drop table if exists tbl_product;
create table tbl_product(
id int primary key,
name varchar(255)
) ENGINE=InnoDB default CHARSET utf8;
MySQL支持哪些存储引擎
show engines \G
/*
mysql> show engines \G
*************************** 1. row ***************************
Engine: MEMORY
Support: YES
Comment: Hash based, stored in memory, useful for temporary tables
Transactions: NO
XA: NO
Savepoints: NO
*************************** 2. row ***************************
Engine: MRG_MYISAM
Support: YES
Comment: Collection of identical MyISAM tables
Transactions: NO
XA: NO
Savepoints: NO
*************************** 3. row ***************************
Engine: CSV
Support: YES
Comment: CSV storage engine
Transactions: NO
XA: NO
Savepoints: NO
*************************** 4. row ***************************
Engine: FEDERATED
Support: NO
Comment: Federated MySQL storage engine
Transactions: NULL
XA: NULL
Savepoints: NULL
*************************** 5. row ***************************
Engine: PERFORMANCE_SCHEMA
Support: YES
Comment: Performance Schema
Transactions: NO
XA: NO
Savepoints: NO
*************************** 6. row ***************************
Engine: MyISAM
Support: YES
Comment: MyISAM storage engine
Transactions: NO
XA: NO
Savepoints: NO
*************************** 7. row ***************************
Engine: InnoDB
Support: DEFAULT
Comment: Supports transactions, row-level locking, and foreign keys
Transactions: YES
XA: YES
Savepoints: YES
*************************** 8. row ***************************
Engine: BLACKHOLE
Support: YES
Comment: /dev/null storage engine (anything you write to it disappears)
Transactions: NO
XA: NO
Savepoints: NO
*************************** 9. row ***************************
Engine: ARCHIVE
Support: YES
Comment: Archive storage engine
Transactions: NO
XA: NO
Savepoints: NO
9 rows in set (0.00 sec)
*/
MySQL支持9个存储引擎,当前版本8.0.26支持8种存储引擎,不同版本支持情况不同。
MySQL常用存储引擎
-
MyISAM
它管理的表具有以下特征:
(1)使用三个文件表示每个表:
1)格式文件:存储表结构的定义(mytable.frm)
2)数据文件:存储表行的内容(mytable.MYD)
3)索引文件:存储表上索引(mytable.MYI):索引是一本书的目录,像字典的目录,缩小扫描范围
提示:对于一张表来说,只要是主键,或者加有unique约束的字段上会自动创建索引。
MyISAM的特点:
可被转换为压缩、只读表来节省空间。这是这种存储引擎的优势!!!
-
InnoDB
这是MySQL默认的存储引擎,同时也是一个重量级的存储引擎。
InnoDB存储引擎最主要的特点是:非常安全!
它管理的表具有以下主要特征:
1)每个InnoDB表在数据库目录中以
.frm
格式文件表示 2)InnoDB 表空间 tablespace 被用于存储表的内容。
3)提供一组用来记录事务性活动的日志文件
4)用COMMIT(提交)、SAVEPOINT 以及 ROLLBACK(回滚)来支持事务处理
5)提供全ACID兼容
6)在MySQL服务器崩溃后提供自动恢复
7)多版本(MVCC)和行级锁定
8)支持外键及引用的完整性,包括级联删除和更新
InnoDB最大的特点就是:
支持事务,以保证数据的安全。但是效率不是很高,并且也不能压缩,不能转换为只读。不能很好的节省空间。
-
MEMORY
使用MEMORY存储引擎的表,其数据存储在内存中(一断电就消失),且行的长度固定。
这两个特点始得MEMORY存储引擎非常快!
它管理的表有以下特征:
1)在数据库目录内,每个表均以 .frm 格式的文件表示。
2)表数据及索引被存储在内存中。(目的就是快,查询快。)
3)表级锁定机制。
4)不能包含 TEXT 或 BLOB 字段。
MEMORY 存储引擎以前被称为 HEAP 引擎。
MEMORY 引擎优点:查询效率是最高的。不需要和硬盘交互。
MEMORY 引擎缺点:不安全,关机之后数据消失。因为数据和索引都是在内存当中。
二、事务(必须掌握⭐⭐⭐⭐⭐)
什么是事务
什么是事务?
答:一个事务其实就是一个完整的业务逻辑!
什么是一个完整的业务逻辑?
例:假设转账,从A账户向B账户中转账10000元。
-->将A账户中的钱减去10000元(update),再将B账户中的钱加上10000元(update)。
这就是一个完整的业务逻辑!!
以上的操作是一个最小的工作单元,要么同时成功,要么同时失败(不可能一个成功一个失败,不然钱就乱了),不可再分。
只有DML语句才有事务这一说(insert、delete、update),其他语句和事务无关!!!
因为只有以上的三个语句是对数据库表中数据进行增、删、改的。操作一旦涉及到数据的增、删、改,那么就一定要考虑安全问题。
数据安全第一位!
一个问题
假设所有的业务,都只需要一条DML语句就能完成,还有必要存在事务机制吗?
答:正是因为做某件事的时候,需要多条DML语句共同联合起来才能完成(例如转账就需要两条update),所以需要事务的存在。如果任 何一件复杂的事都能一条DML语句搞定,那么事务就没有存在的价值了。
事务的本质:一个事务其实就是多条DML语句要么同时成功,要么同时失败!
事务的原理
事务是怎么做到多条DML语句同时成功和同时失败的呢?
InnoDB存储引擎:提供一组用来记录事务性活动的日志文件。
事务开启了!
insert
insert
delete
update
update
事务结束了!
在事务的执行过程中,每一条DML的操作都会被记录到“事务性活动的日志文件”中。(操作一下记录一下)
在事务的执行过程中,我们可以提交事务
,也可以回滚事务
。
提交事务:
清空事务性活动的日志文件,将数据全部彻底持久化到数据表中。
提交事务标志着,事务的结束。并且是一种全部成功的结束。
回滚事务:
将之前的所有DML操作全部撤销,并且清空事务性活动的日志文件。
回滚事务标志着,事务的结束。并且是一种全部失败的结束。
怎么操作事务
事务对应的英语单词是:transaction
-- 提交事务
commit;
-- 回滚事务,回滚只能回滚到上一次的提交点。
rollback;
测试一下,在MySQL当中默认的事务行为是怎样的?
执行了三条DML语句,然后回滚,却失败了。说明MySQL默认情况下是支持自动提交事务的。(自动commit;)
没执行一条DML语句,就自动commit一次。所以没办法回滚了。
这种自动提交实际上是不符合我们的开发习惯的,因为一个事务通常是需要多条DML语句共同执行才能完成的,为了保证数据的安全,必须要求同时成功之后再提交,所以不能执行一条就提交一条。
怎么将MySQL的自动提交事务机制关闭掉呢?
-- 在执行所有DML语句前,先执行这条命令,开启事务,关闭自动提交。
start transaction;
注意:当开启事务后,最后在commit;之后后,数据就彻底持久化了,就不能回滚了!!!
事务的4个特性
A:原子性
说明事务是最小的工作单元。不可再分。
C:一致性
所有事务要求,在同一个事务当中,所有的操作必须同时成功或者同时失败,以保证数据的一致性。
I:隔离性
A事务和B事务之间具有一定的隔离性。
教室A和教室B之间有一道墙,这道墙就是隔离性。
A事务在操作一张表的同时,另一个事务B也操作这张表的话会怎么样?
D:持久性
事务最终结束的一个保障。事务提交,就相当于将没有保存到硬盘上的数据保存到硬盘上。
研究一下事务的隔离性
A教室和B教师中间有一道墙,这道墙可以很厚,也可以很薄。这就是事务的隔离级别。这道墙越厚,表示隔离级别就越高。
事务和事务之间的隔离级别有哪些呢?4个级别
-
读未提交:read uncommitted(最低的隔离级别)
什么是读未提交:事务A可以读取到事务B未提交的数据。
这种隔离级别存在的问题:脏读现象!(dirty read)我们称读到了脏数据。
这种隔离一般都是理论上的,大多数的数据库隔离级别都是二档起步!
-
读已提交:read committed
什么是读已提交:事务A只能读取到事务B提交之后的数据。
这种隔离级别解决了什么问题:解决了脏读的现象。
这种隔离级别存在的问题:不可重复读取数据。
什么是不可重复读取数据:
在事务开启之后,第一次读到的数据是3条,当前事务还没有结束,可能第二次再读取的时候,读到的数据是4条,3不等于4,称为不可重复读取。(你第一次读的时候没提交,后来他提交了,你再读一次就不一样了)
这种隔离级别是比较真实的数据,每一次读到的数据是绝对的真实。Oracle数据库默认的隔离级别是:读已提交。
-
可重复读:repeat read
什么是可重复读:
事务A开启之后,不管是多久,每一次在事务A中读取到的数据都是一致的,都是事务开启时的那个数据。即使事务B将数据已经修改,并且提交了,事务A读取到的数据还是没有发生改变。
可重复读解决了什么问题:解决了不可重复读的问题。(屁话)
可重复读存在的问题:可能会出现幻影读。每一次读取到的数据都是幻象,不够真实。
它有什么用呢?一个例子:
银行要查总账,所以需要执行一条select语句,由于数据量巨大,这条select语句要从下午1点开始执行到下午3点才能结束。可是1点到3点之间可能会有人存款和取款。
为了保证别人最新的存款和取款对我查询的数据不受影响(我就要查直到下午1点这个时刻的银行总账),所以需要第三档。
最终的结果都是1点钟时候的结果。
MySQL的默认级别就是三档起步!
-
序列化/串行化:serializable(最高的隔离级别)
这个最高隔离级别,效率最低。解决了所有问题
这个隔离级别表示事务排队,不能并发(事务同步,类似于
锁
)每一次读取到的数据都是最真实的,并且效率是最低的。
验证各种隔离级别
-- 查看隔离级别
select @@transaction_isolation;
验证:read uncommitted
-- 开启两个MySQL客户端,都设置当前会话的事务级别为 READ-UNCOMMITTED
set session transaction isolation level read uncommitted;
/*
分别执行命令,查看现象。
事务A 事务B
=============================================================
start transaction;
start transaction;
select * from tbl_test;
insert into tbl_test values('zhangsan');
select * from tbl_test;
rollback;
select * from tbl_test;
*/
剩下的自行验证吧。。。懒了
三、索引
什么是索引
什么是索引(index)?
索引是在数据库表的字段上添加的,是为了提高查询效率存在的一种机制。
一张表的一个字段可以添加一个索引,当然,多个字段联合起来也可以添加索引。索引相当于一本书的目录,是为了缩小扫描范围而存在的一种机制。
对于一本字典来说,查找某个汉字有两种方式:
第一种方式:一页一页挨着找,直到找到为止,这种查找方式属于全字典扫描,效率比较低。
第二种方式:先通过目录(索引)去定位一个大概的位置,然后直接定位到这个位置,做局域性扫描,缩小扫描的范围,快速的查找。这种查找方式属于通过索引检索,效率较高。
select * from tbl_user where name = 'jack';
/*
以上这条sql语句会去name字段上扫描,为什么?
因为查询条件是:name = 'jack'
如果name字段上没有添加索引(目录),或者说没有给name字段创建索引,MySQL会进行全扫描,会将name字段上的每一个值都比对一遍。效率比较低。
*/
MySQL在查询方面主要就是两种方式:
- 全表扫描
- 根据索引检索
注意:
在实际中,汉语字典前面的目录是有排序的,按照a、b、c、d、...排序,为什么排序呢?因为只有排序了才会有区间查找这一说。
缩小扫描范围其实就是扫描某个区间罢了!
在MySQL数据库当中索引也是需要排序的,并且这个索引的排序和TreeSet数据结构相同。TreeSet(TreeMap)底层是一个自平衡的二叉树!
在MySQL当中索引是一个B-Tree数据结构。遵循左小右大的原则存放。采用中序遍历方式遍历取数据。
索引的实现原理
假设有一张用户表:tbl_user
id(PK) | name | 每一行记录在硬盘上都有物理存储编号 |
---|---|---|
100 | 张三 | 0x1111 |
120 | 李四 | 0x2222 |
99 | 王五 | 0x8888 |
88 | 赵六 | 0x9999 |
101 | 法外狂徒 | 0x6666 |
55 | 暴怒骑士 | 0x5555 |
提醒1:在任何数据库当中主键都会自动添加索引对象,id字段上自动有索引,因为id是PK。另外在MySQL当中,一个字段上如果有unique约束的化,也会自动创建索引对象。
提醒2:在任何数据库当中,任何一张表的任何一条记录在硬盘存储上都有一个硬盘的物理存储编号。
提醒3:在MySQL当中,索引是一个单独的对象,不同的存储引擎以不同的形式存在,在MyISAM存储引擎中,索引存储在一个.MYI文件中。在InnoDB存储引擎中,索引存储在一个逻辑名称叫做tablespace当中。在MEMORY存储引擎当中索引被存储在内存当中。不管索引存储在哪里,索引在MySQL当中都是一个树的形式存在。(自平衡二叉树:B-Tree)
索引的大致原理,MySQL的索引比这个还要复杂。
在MySQL当中,主键上以及unique约束的字段上回自动添加索引。
什么条件下,我们会考虑给字段添加索引呢?
条件1:数据量庞大。
条件2:该字段经常出现在where的后面,以条件的形式存在,也就是说这个字段总是被扫描。
条件3:该字段很少的DML(增删改)操作。(因为DML之后,索引需要重新排序)
建议不要随意添加索引,因为索引也是需要维护的,太多的话反而会降低系统的性能。建议通过主键查询,建议通过unique约束的字段
进行查询,效率是比较高的。
索引的创建和删除
-- 创建索引
-- 给emp表的ename字段添加索引,起名:emp_ename_index
create index emp_ename_index on emp(ename);
-- 删除索引
-- 将emp表上的emp_ename_index索引对象删除。
drop index emp_ename_index on emp;
测试索引
explain:可以查询sql的执行计划,并没有执行sql。
索引失效
-
失效的第一种情况
select * from emp where ename like '%T';
这种情况,ename上即使添加了索引,也不会走索引,为什么?
因为模糊匹配当中以“%”开头了!尽量避免模糊查询的时候以“%”开始(当然不得已的情况下,该用还得用)。这是一种优化手段。
-
失效的第二种情况
使用
or
的时候可能失效。如果使用or
,那么要求or
两边的条件字段都要有索引,才会走索引,如果其中一边有一个字段没有索引,那么另一个字段上的索引也会实现。所以这就是为什么不建议使用or
的原因。所以尽量用 union 来代替有 or 的查询
-
失效的第三种情况
使用复合索引的时候,没有使用左边的字段查找,索引失效。
-- 左边的字段就是 job 字段 create index emp_job_sal_index on emp(job,sal);
-
失效的第四种情况
在where当中索引列参加了运算,索引失效。
-- 正常使用索引 select * from emp where sal = 800; -- 正常使用索引 select * from emp where sal = 800+1; -- 不使用索引 select * from emp where sal+1 = 800;
-
失效的第五种情况
在where当中索引列使用了函数。
-- 索引失效 select * from emp where lower(ename) = 'smith';
索引的分类
- 单一索引
- 复合索引
- 主键索引
- 唯一性索引
四、视图
什么是视图
什么是视图?
view:站在不同的角度去看待同一份数据。
视图的创建和删除
-- 快速复制一张表
create table emp2 as select * from emp;
-- 创建视图对象
create view emp2_view as select * from emp2;
-- 删除视图
drop view emp2_view;
注意:只有DQL语句才能以view的形式创建。
视图的作用
我们可以面向视图对象进行增删改查,对视图对象的增删改查,会导致原表被操作。(视图的特点:通过对视图的操作,会影响到原表的数据)
小试牛刀
-- 复制出两张模拟表
create table emp2 as select * from emp;
create table dept2 as select * from dept;
-- 创建一个多表关联的视图对象
create view
emp2_dept2_view
as
select
e.ename, e.sal, d.dname
from
emp2 e
join
dept2 d
where
e.deptno = d.deptno;
-- 查询视图对象
select * from emp2_dept2_view;
-- 面向视图更新
update emp2_dept2_view set sal = 1000 where ename='SMITH';
-- 面向视图插入
insert into emp2_dept2_view values('WZZ',5000,'CODER');
可是,好像无法向多表关联视图对象插入数据。
原因是:
所创建的视图依赖了多个基本表,所以不能插入数据。
如果视图只依赖了一个基本表的话,可以插入数据。
拓展:
1.插入视图数据:视图依赖多个基本表时,不能insert数据。依赖一张基本表时,插入的数据必须包含所有不能为空的列。
2.修改视图数据:视图依赖多个基本表时,一次只能变动一个基本表。
3.删除视图数据:视图依赖多个基本表时,不能用delete。(删除数据会影响基本表,删除视图不会影响基本表)
视图是实际开发中的作用
视图是用来简化sql语句的。
问:假设有一条非常复杂的sql语句(非常非常复杂,sql语句能写一页A4纸),而且这条sql语句需要在不同的位置上反复使用。每一次使用这个sql语句的时候都需要重新编写,很长,很麻烦,怎么办?
答:可以把这条复杂的sql语句以视图对象的形式新建。在需要编写这条sql语句的位置直接使用视图对象,可以大大简化开发。并且利于后期的维护,因为修改的时候也只需要修改一个位置就行,只需要修改视图对象所映射的sql语句。
我们以后面向视图开发的时候,使用视图的时候可以像使用表一样。可以对视图进行增删改查操作。视图不是在内存当中,视图对象也是存储在硬盘上的,不会消失。
提醒一下:
视图对应的语句只能是DQL语句(select)。
但是视图对象创建完成之后,可以对视图进行增删改查操作。
小插曲:
增删改查,又叫做:CRUD。
CRUD是在企业中程序员之间沟通的术语。一般我们很少说增删改查,一般都说CRUD。
C:Create(增) R:Retrieve(查:检索) U:Update(改) D:Delete(删)
五、数据库设计的三范式
什么是数据库设计范式
什么是数据库设计范式?
数据库表的设计依据。教你怎么进行数据库表的设计。
数据库设计范式共有3个。
第一范式:要求任何一张表都必须有主键,每一个字段原子性不可再分。
第二范式:建立在第一范式的基础之上,要求所有非主键字段完全依赖主键,不要产生部分依赖。
第三范式:建立在第二范式的基础之上,要求所有非主键字段直接依赖主键,不要产生传递依赖。
声明:三范式是面试官经常问的,所以一定要熟记在心!
设计数据库表的时候,按照以上的范式进行,可以避免表中数据的冗余、空间的浪费。
第一范式
最核心、最重要的范式,所有表的设计都需要满足:必须有主键,并且每一个字段都是原子性不可再分。
学生编号 | 学生姓名 | 联系方式 |
---|---|---|
1001 | 张三 | zs@qq.com,13599999999 |
1002 | 李四 | ls@qq.com,13699999999 |
1003 | 王五 | ww@qq.com,13799999999 |
以上学生表,满足第一范式吗?
不满足。第一:没有主键;第二:联系方式可以分为邮箱和电话。
学生编号(PK) | 学生姓名 | 邮箱 | 电话 |
---|---|---|---|
1001 | 张三 | zs@qq.com | 13599999999 |
1002 | 李四 | ls@qq.com | 13699999999 |
1003 | 王五 | ww@qq.com | 13799999999 |
第二范式
建立在第一范式的基础之上,要求所有非主键字段必须完全依赖主键,不要产生部分依赖(复合主键)。
这张表描述了学生和老师之间的关系。
学生编号 | 学生姓名 | 教师编号 | 教师姓名 |
---|---|---|---|
1001 | 张三 | 001 | 王老师 |
1002 | 李四 | 002 | 赵老师 |
1003 | 王五 | 001 | 王老师 |
1001 | 张三 | 002 | 赵老师 |
这张表描述了学生和老师的关系:(1个学生可能有多个老师,1个老师有多个学生)
典型的多对多关系!!
分析以上的表是否满足第一范式?
不满足第一范式。
怎么满足第一范式呢?修改》》》
学生编号 | 教师编号 | 学生姓名 | 教师姓名 |
---|---|---|---|
1001 | 001 | 张三 | 王老师 |
1002 | 002 | 李四 | 赵老师 |
1003 | 001 | 王五 | 王老师 |
1001 | 002 | 张三 | 赵老师 |
(学生编号,教师编号),两个字段联合作主键,复合主键(PK:学生编号+教师编号)
经过修改之后,以上的表满足的第一范式。但是满足第二范式吗?
不满足。“张三”依赖1001,“王老师”依赖001,显然产生了部分依赖。
产生部分依赖有什么缺点?
数据冗余了,空间浪费了。“张三”重复了,“王老师”重复了。
为了让以上的表满足第二范式,你需要这样设计:
表示多对多的关系需要使用三张表!!!学生表、教师表、学生教师关系表
学生表
学生编号(PK) | 学生姓名 |
---|---|
1001 | 张三 |
1002 | 李四 |
1003 | 王五 |
教师表
教师编号(PK) | 教师姓名 |
---|---|
001 | 王老师 |
002 | 赵老师 |
学生教师关系表
id(PK) | 学生编号(FK) | 教师编号(FK) |
---|---|---|
1 | 1001 | 001 |
2 | 1002 | 002 |
3 | 1003 | 001 |
4 | 1001 | 002 |
多对多怎么设计?
多对多,三张表,关系表两个外键!!!!!!!!!!!!!!!!
第三范式
第三范式建立在第二范式的基础之上,要求所有非主键字典必须直接依赖主键,不要产生传递依赖。
学生编号(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 |
一对多怎么设计?
一对多,两张表,多的表加外键!!!!!!!!!!
总结表的设计
一对多:
一对多,两张表,多的表加外键!!!!!!!
多对多:
多对多,三张表,关系表两个外键!!!!!!
一对一:
一对一可能一张表,也可能多张表。
一对一放到一张表中不就行了吗?为啥还要拆分表?
在实际的开发中,可能存在一张表字段太多,太庞大。这个时候需要拆分表。
一对一怎么设计?
没有拆分表之前:一张表
id | login_name | login_pwd | real_name | address | |
---|---|---|---|---|---|
1 | zhangsan | 123 | 张三 | zs@qq.com | ... |
2 | lisi | 123 | 李四 | ls@qq.com | ... |
这种庞大的表建议拆分成两张表:
登录信息表
id | login_name | login_pwd |
---|---|---|
1 | zhangsan | 123 |
2 | lisi | 123 |
用户详细信息表
id | real_name | address | login_id(FK+unique) | |
---|---|---|---|---|
100 | 张三 | zs@qq.com | ... | 1 |
101 | 李四 | ls@qq.com | ... | 2 |
一对一,外键唯一!!!!!!!!!!!!
嘱咐一句话(描述完三范式说的)
数据库设计三范式是理论上的
实践和理论有的时候有偏差。
最终目的都是为了满足客户的需求,有的时候会拿冗余换执行速度。
因为在sql当中,表和表之间连接次数越多,效率越低。(笛卡尔积)
有的时候可能会存在冗余,但是为了减少表的连接次数,这样做也是合理的,并且对于开发人员来说,sql语句的编写难度也会降低。
面试的时候把这几句话说上:他就不会认为你是初级程序员了!!!