学习笔记-SQL语言基础及数据库基本原理

  数据库基本存储原理

 基本存储单元——

数据库文件存储是以页为存储单元的,一个页是8K8192Byte),一个页就可以存放N行数据。我们表里的数据都是存放在页上的,这种叫数据页。还有一种页存放索引数据的,叫索引页。

同时,页也是IO读取的最小单元(物理IO上不是按行读取),也是所有权的最小单位。如果一页中包含了表A的一行数据,这页就只能存储表A的行数据了。或是一页中包含了索引B的条目,那这页也仅仅只能存储索引B的条目了。每页中除去存储数据之外,还存储一些页头信息以及行偏移以便SQL Server知道具体每一行在页中的存储位置。

数据库的基本物理存储单元是页,一个表由很多个页组成,那这些页又是如何组织的呢?我们一般都会对表创建索引,这些索引又是如何存储的呢?不要走开,请看下文。

 /索引的存储结构

如下图,是一个B树(二叉搜索树)的示例,都是小的元素放左边,大的元素放右边,依次构造的,比如要查找元素9,从根节点开始,只要比较三次就找到他了,查询效率是非常高的。

B+树和B-树都是B树的变种,或者说是更加高级的复杂版本(关于B树的资料,有兴趣可以自己去学习,这里只是抛砖引玉)。B+树和B-树是数据库中广泛应用的索引存储结构,它可以极大的提高数据查找的效率。前面说了数据库存储的基本单元是页,因此,索引树上的节点就是页了。

因此不难看出,索引的主要优点和目的就是为了提高查询效率。

 

为了保证数据的查询效率,当新增、修改、删除数据的时候,都需要维护这颗索引树,就可能会出现分裂、合并节点(页)的情况(这是树的结构所决定的,想要更好理解这一点,可以尝试自己代码实现一下B-B+树)。好!重点来了!

索引的缺点:

  • 当新增、修改、删除数据的时候,需要维护索引树,有一定的性能影响;
  • 同上面,在频繁的树维护过程中,B树的页拆分、合并会造成大量的索引碎片,又会极大的印象查询效率 ,因此索引还需要维护;
  • 非聚集索引需要额外的存储空间,不过这个一般问题都不是很大,但是需要注意的一个问题;

 聚集索引

聚集索引决定了表数据的物理存储顺序,也就是说表的物理存储是根据聚集索引结构进行顺序存储的,因此一个表只能有一个聚集索引。如下图,就是一个聚集索引的树结构:

  • 所有数据都在叶子节点的页上,在叶子节点(数据页)之间有一个链指针,这是B+树的特点;
  • 非叶子节点都是索引页,存储的就是聚集索引字段的值;
  • 表的物理存储就是依据聚集索引的结构的,一个表只能有一个聚集索引;

聚集索引的所有的数据都存储在叶子节点上,数据查询的复杂度都是一样的(树的深度),按照聚集索引列查找数据效率是非常高的。上面说了,聚集索引决定了表的物理存储结构,那如果没有创建聚集索引,会如何呢?——表内的所有页都无序存放,是一个无序的堆结构。堆数据的查询就会造成表扫描,性能是非常低的。

因此聚集索引的的重要性不言而喻,一般来说,大多会对主键建立聚集索引,大多数普通情况这么做也可以。但实际应用应该尊从一个原则就是频繁使用的、排序的字段上创建聚集索引

 非聚集索引

除了聚集索引以外的其他索引,都称之为非聚集索引,非聚集索引一般都是为了优化特定的查询效率而创建的。非聚集索引也是B树(B+树和B-树)的结构,与非聚集索引的存储结构唯一不一样的,就是非聚集索引中不存储真正的数据行,因为在聚集索引中已经存放了所有数据,非聚集索引只包含一个指向数据行的指针即可。

  • 非聚集索引的创建会单独创建索引文件来存储索引结构,会占用一定存储空间,就是用空间换时间;
  • 非聚集索引的目的很单纯:提高特定条件的查询效率,一个表有可能根据多种查询需求创建多个非聚集索引;

https://images.cnblogs.com/cnblogs_com/changbluesky/WindowsLiveWriter/SQLServer6IndexandTSQLTuning_DD36/image_4.png

数据查询SQL简单来看,分为两个部分:SELECT****  Where ****,因此索引的创建也是根据这两部分来决定的。根据这两点,有两种主要的索引形式:复合索引和覆盖索引,在实际使用中,根据具体情况可能都会用到,只要能提高查询效率就是好索引。

覆盖索引:就是在索引中包含的数据列(非索引列,SELECT需要的列),这样在使用该索引查询数据时就不会再进行键查找(也叫书签查找)了。

复合索引:主要针对Where中有多个条件的情况,索引包含多个数据列。在使用复合索引时,应注意多个索引键的顺序问题,这个是会影响查询效率的,一般的原则是唯一性高的放前面,还有就是SQl语句中Where条件的顺序应该和索引顺序一致。

 

 索引碎片

前面说过了,索引在使用一段时间后(主要是新增、修改、删除数据,如果该页已经存储满了,就要进行页的拆分,频繁的拆分,会产生较多的索引碎片)会产生索引碎片,这就造成了索引页在磁盘上存储的不连续。会造成磁盘的访问使用的是随机的i/o,而不是顺序的i/o读取,这样访问索引页会变得更慢。如果碎片过多,数据库是可会能不使用该索引的(她嫌弃你太慢了,数据库会选择一个更优的执行计划)。

解决这个问题主要是两种方法:

第一种是预防:设置页的填充因子

意思就是在页上设置一段空白区域,在新增数据的时候,可以使用这段空白区域,可以一定的避免页的拆分,从而减少索引碎片的产生。

填充因子就是用来描述这种页中填充数据的一个比例,一般默认是100%填充的。如果我们修改填充因子为80%,那么页在存储数据时,就会剩余20%的剩余空间,这样在下次插入的时候就不会拆分页了。 那么是不是我们可以把填充因子设置低一点,留更多的剩余空间,不是很好嘛?当然也不好,填充因子设置的低,会需要分配更多的存储空间,叶子节点的深度会增加,这样是会影响查询效率的,因此,这是要根据实际情况而定的。

那么一般我们是怎么设置填充因子的呢,主要根据表的读写比例而定的。如果读的多,填充因子可以设置高一点,如100%,读写各一半,可以80~90%;修改多可以设置50~70%

第二种是索引修复:定期对索引进行检查、维护,写一段SQL检查索引的碎片比例,如果碎片过多,进行碎片修复或重建,定期执行即可。具体可以参考本文末尾的相关参考资料。

https://images0.cnblogs.com/blog/151257/201308/13102931-26c04edc675741899452a9ab514d1072.png

 索引使用总结

  • 创建索引的的字段尽量小,最好是数值,比如整形int等;
  • 对于频繁修改的字段,尽量不要创建索引,维护索引的成本很高,而且更容易产生索引碎片;
  • 定期的索引维护,如索引碎片的修复等;
  • 不要建立或维护不必要的重复索引,会增加修改数据(新增、修改、删除数据)的成本;
  • 使用唯一性高的字段创建索引,切不可在性别这样的低唯一性的字段上创建索引;
  • SQL语句中,尽量不要在Where条件中使用函数、运算符或表达式计算,会造成索引无法正常使用;
  • 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描;
  • 应尽量避免在 where 子句中使用!=<>操作符,否则将导致引擎放弃使用索引而进行全表扫描;

  事务与锁

事务就是作为一个逻辑工作单元的SQL语句,如果任何一个语句操作失败那么整个操作就被失败,以后操作就会回滚到操作前状态,或者是上个节点。为了确保要么执行,要么不执行,就可以使用事务。而锁是实现事务的关键,锁可以保证事务的完整性和并发性。

这部分的理论性太强了,不如看看下文章末尾的参考资料更好(博客园的高质量文章还是相当多的),简单总结一下就好了!

和在.NET中的锁用途类似,数据库中的锁也是为了解决在并发访问时出现各种冲突的一种机制。

 

  题目答案解析:

1. 索引的作用?和它的优点缺点是什么?

索引就一种特殊的查询表,数据库的搜索引擎可以利用它加速对数据的检索。索引很类似与现实生活中书的目录,不需要查询整本书内容就可以找到想要的数据。缺点是它减慢了数据录入的速度,同时也增加了数据库的尺寸大小。

2. 介绍存储过程基本概念和 她的优缺点

存储过程是一个预编译的SQL语句,他的优点是允许模块化的设计,也就是说只需创建一次,在该程序中就可以调用多次。例如某次操作需要执行多次SQL,就可以把这个SQL做一个存储过程,因为存储过程是预编译的,所以使用存储过程比单纯SQL语句执行要快。缺点是可移植性差,交互性差。

3. 使用索引有哪些需要注意的地方?

  • 创建索引的的字段尽量小,最好是数值,比如整形int等;
  • 对于频繁修改的字段,尽量不要创建索引,维护索引的成本很高,而且更容易产生索引碎片;
  • 定期的索引维护,如索引碎片的修复等;
  • 不要建立或维护不必要的重复索引,会增加修改数据(新增、修改、删除数据)的成本;
  • 使用唯一性高的字段创建索引,切不可在性别这样的低唯一性的字段上创建索引;
  • SQL语句中,尽量不要在Where条件中使用函数、运算符或表达式计算,会造成索引无法正常使用;
  • 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描;
  • 应尽量避免在 where 子句中使用!=<>操作符,否则将导致引擎放弃使用索引而进行全表扫描;

4. 索引碎片是如何产生的?有什么危害?又该如何处理?

索引在使用一段时间后(主要是新增、修改、删除数据,如果该页已经存储满了,就要进行页的拆分,频繁的拆分,会产生较多的索引碎片)会产生索引碎片。

索引碎片会严重印象数据的查询效率,如果碎片太多,索引可能不会被使用。

碎片的处理方式主要有两种:

第一种是预防:设置页的填充因子

意思就是在页上设置一段空白区域,在新增数据的时候,可以使用这段空白区域,可以一定的避免页的拆分,从而减少索引碎片的产生。

填充因子就是用来描述这种页中填充数据的一个比例,一般默认是100%填充的。如果我们修改填充因子为80%,那么页在存储数据时,就会剩余20%的剩余空间,这样在下次插入的时候就不会拆分页了。 那么是不是我们可以把填充因子设置低一点,留更多的剩余空间,不是很好嘛?当然也不好,填充因子设置的低,会需要分配更多的存储空间,叶子节点的深度会增加,这样是会影响查询效率的,因此,这是要根据实际情况而定的。

那么一般我们是怎么设置填充因子的呢,主要根据表的读写比例而定的。如果读的多,填充因子可以设置高一点,如100%,读写各一半,可以80~90%;修改多可以设置50~70%

第二种是索引修复:定期对索引进行检查、维护,写一段SQL检查索引的碎片比例,如果碎片过多,进行碎片修复或重建,定期执行即可。具体可以参考本文末尾的相关参考资料。

5. 锁的目的是什么?

主要解决多个用户同时对数据库的并发操作时会带来以下数据不一致的问题:

  • 丢失更新,同时修改一条数据
  • 读脏,A修改了数据后,B读取后A又取消了修改,B读脏
  • 不可重复读,A用户读取数据,随后B用户读取该数据并修改,此时A用户再读取数据时发现前后两次的值不一致
  • 还有一种是幻读,这个情况好像不多。

并发控制的主要方法是封锁,锁就是在一段时间内禁止用户做某些操作以避免产生数据不一致

6. 锁的粒度有哪些?

  • 数据库锁:锁定整个数据库,这通常发生在整个数据库模式改变的时候。
  • 表锁:锁定整个表,这包含了与该表相关联的所有数据相关的对象,包括实际的数据行(每一行)以及与该表相关联的所有索引中的键。
  • 区段锁:锁定整个区段,因为一个区段由8页组成,所以区段锁定是指锁定控制了区段、控制了该区段内8个数据或索引页以及这8页中的所有数据行。
  • 页锁:锁定该页中的所有数据或索引键。
  • 行或行标识符:虽然从技术上将,锁是放在行标识符上的,但是本质上,它锁定了整个数据行。

7. 什么是事务?什么是锁?

事务就是被绑定在一起作为一个逻辑工作单元的SQL语句分组,如果任何一个语句操作失败那么整个操作就被失败,以后操作就会回滚到操作前状态,或者是上个节点。为了确保要么执行,要么不执行,就可以使用事务。要将所有组语句作为事务考虑,就需要通过ACID测试,即原子性,一致性,隔离性和持久性。 
锁是实现事务的关键,锁可以保证事务的完整性和并发性。

8. 视图的作用,视图可以更改么?

视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们。

视图的目的在于简化检索,保护数据,并不用于更新。

9. 什么是触发器(trigger)? 触发器有什么作用?

触发器是数据库中由一定时间触发的特殊的存储过程,他不是由程序掉用也不是手工启动的。触发器的执行可以由对一个表的insert,delete, update等操作来触发,触发器经常用于加强数据的完整性约束和业务规则等等。

10. SQL里面IN比较快还是EXISTS比较快?

这个题不能一概而论,要根据具体情况来看。IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。

如果查询语句使用了not in,那么对内外表都进行全表扫描,没有用到索引;而not exists的子查询依然能用到表上的索引。所以无论哪个表大,用not exists都比not in 要快。参考资料:http://www.cnblogs.com/seasons1987/archive/2013/07/03/3169356.html

11. 维护数据库的完整性和一致性,你喜欢用触发器还是自写业务逻辑?为什么?

尽可能使用约束,如check、主键、外键、非空字段等来约束。这样做效率最高,也最方便。其次是使用触发器,这种方法可以保证,无论什么业务系统访问数据库都可以保证数据的完整新和一致性。最后考虑的是自写业务逻辑,但这样做麻烦,编程复杂,效率低下。

文章来源:http://www.cnblogs.com/anding

自己总结:

!= <> 都是不等于 用法一致 不包含空值

交叉联接返回左表中的所有行,左表中的每一行与右表中的所有行组合。交叉联接也称作笛卡尔积。 

如果不加任何限制条件,将得到的是数据的笛卡儿积

 

INNER JOIN 内连接=join   隐式  用比较运算符号  a.i=b.id

Left join 左连接 

Right join 右连接

Full join全连接

Cross join 交叉连接   隐式 a.id=1

 

SQL LIKE子句使用通配符运算符比较相似的值。:

百分号 (%):百分号代表零个,一个或多个字符

下划线 (_):下划线表示单个数字或字符

查询以 "g" 开始的: like ‘g%’

查询以 "g" 结尾的: like ‘%g’

第一个字符之后是 "eorge" 的人: LIKE '_eorge'

"C" 开头,然后是一个任意字符,然后是 "r",然后是任意字符,然后是 "er":

LIKE 'C_r_er'

以 "A" 或 "L" 或 "N" 开头的人:: '[ALN]%'

不以 "A" 或 "L" 或 "N" 开头的人: LIKE '[!ALN]%'

 

 Sqlserver不支持limit

 

窗口函数OVER()指定一组行,开窗函数计算从窗口函数输出的结果集中各行的值。

语法结构:排名函数 ( ) OVER ( [ <partition_by子句> ] <order_by子句> ) 

ROW_NUMBER():为每一组的行记录按顺序生成一个唯一的行号。

查询各科成绩前2名的记录

  1. 先从科目倒序 再成绩倒序
  2. 子查询查找各科前两名的人

 

什么是索引?

索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。

举个例子,索引就像我们查字典时用的按拼音或笔画或偏旁部首

 

索引的存储是以B+树(注意和二叉树的区别)结构来存储的,又称索引树

  • 索引节点;
  • 叶子节点

  索引节点按照层级关系,有时又可以分为根节点和中间节点,其本质是一样的,都只包含下一层节点的入口值和入口指针;

叶子节点就不同了,它包含数据,这个数据可能是表中真实的数据行,也有可能是索引列值和行书签,前者对应于聚集索引,后者对应于非聚集索引。

 

 

 

 

聚集索引

根节点包含下一层中间节点的节点指针(页号),节点值(主键值)

中间节点包含下一层页节点的节点指针(页号),节点值(主键值)

总结:

聚集索引的根节点和中间节点是索引页,都只包含下一层的入口指针和入口值(位于存储位置的第一个主键值);

聚集索引的叶节点就是数据页。

 

 

 

数据页里面的数据按照主键值顺序存放

 

总结:

聚集索引就是把数据按主键顺序存储;

因为一张表中的数据只能有一个物理顺序,所以一张表只能有一个主键/聚集索引。

非聚集索引

 

根节点包含下一层中间节点的页号,非聚集索引的键值以及聚集索引的键值

中间页节点同样包含了下一层(叶节点)的页号以及聚集、非聚集键值

叶节点包含聚集、非聚集索引键值以及一个KeyHasValue

 

总结:

  • 非聚集索引的根节点和中间节点是索引页,都只含下一层级的入口指针和入口值(位于存储位置的第一个键值);
  • 非聚集索引的叶节点也是索引页,也存储有聚集索引和非聚集索引的键值;
  • 非聚集索引中的每个索引行(不论是根节点、中间节点还是叶节点)都包含非聚集键值和行定位符(本例为聚集索引键值),此定位符指向聚集索引或堆(没有聚集索引的表)中包含该键值的数据行。

  非聚集索引行中的行定位器可以是指向行的指针,也可以是行的聚集索引键,具体根据如下情况而定:

  • 如果表是堆(意味着该表没有聚集索引),则行定位器是指向行的指针。该指针由文件标识符(ID)、页码和页上的行数生成。整个指针称为行ID (RID)
  • 如果表有聚集索引或索引视图上有聚集索引,则行定位器是行的聚集索引键(本例即为EmployeeId)。如果聚集索引不是唯一的索引,SQL Server将添加在内部生成的值(称为唯一值)以使所有重复键唯一。此四字节的值对于用户不可见。仅当需要使聚集键唯一以用于非聚集索引中时,才添加该值。SQL Server通过使用存储在非聚集索引的叶行内的聚集索引键搜索聚集索引来检索数据行。

 

非聚集索引作用:

非聚集索引一般都是为了优化特定的查询效率

索引覆盖

  • 索引覆盖和非聚集索引的根节点和中间节点一样,都是索引页,都只包含下一层的入口指针和入口值。
  • 索引覆盖的叶节点却稍有不同,多了一列DepartmentCode,此列即为索引覆盖列,而且此列只在叶节点出现,如果查询时,只需返回键值列和索引覆盖列,则只需索引查找,肯本无需访问数据页,不仅提高了性能,而且节省占用空间。

 

 

如果表有聚集索引(区段结构),那么书签就是从非聚集索引找到聚集索引后,利用聚集索引定位到数据。此处的书签就是聚集索引。如果表没有聚集索引(堆结构)。那么扫描非聚集索引后,通过RID定位到数据,那么此处书签就是RID

  所谓的书签查找,就是通过聚集索引,然后利用聚集索引或RID定位到数据。

书签查找(RID查找、键查找)

 

覆盖索引又可以称为索引覆盖。
  解释一: 就是select的数据列只用从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖。
  解释二: 索引是高效找到行的一个方法,当能通过检索索引就可以读取想要的数据,那就不需要再到数据表中读取行了。如果一个索引包含了(或覆盖了)满足查询语句中字段与条件的数据就叫做覆盖索引。
  解释三: 是非聚集组合索引的一种形式,它包括在查询里的SelectJoinWhere子句用到的所有列(即建立索引的字段正好是覆盖查询语句[select子句]与查询条件[Where子句]中所涉及的字段,也即,索引包含了查询正在查找的所有数据)

 

 

 

BEGIN TRAN:设置起始点。

COMMIT TRAN:使事务成为数据库中永久的、不可逆转的一部分。

ROLLBACK TRAN:本质上说想要忘记它曾经发生过。

SAVE TRAN:创建一个特定标记符,只允许部分回滚。

begin catch

end catch 异常

 

B的字段是从表A获取,表A的字段改变了,表B的相应字段也需要改变,这就叫一致性

 

避免事务中的用户交互:尽量不要在事务中要求用户响应,因为事务持有的任何锁只有在事务提交或回滚后才会释放,等待用户响应的时间,容易导致阻塞或死锁。

由一个基表定义的视图,只含有基表的主键或候补键,并且视图中没有用表达式或函数定义的属性,才允许更新。

猜你喜欢

转载自blog.csdn.net/qq_25744257/article/details/82763398
今日推荐