第5章_数据库相关(二)

18 阅读21分钟

@TOC

11.触发器

触发器( TRIGGER ) 是数据库提供给程序员和DBA 用来保证数据完整性的一种方法, 它是与表事件相关的特殊的存储过程, 是用户定义在表上的一类由事件驱动的特殊过程。触发器的执行不是由程序调用, 也不是由手工启动, 而是由事件来触发的, 其中, 事件是指用户对表的增( INSERT ) 、删(DELETE) 、改( 即更新UPDATE ) 等操作。触发器经常被用于加强数据的完整性约束和业务规则等。   触发器与存储过程的区别在于: 存储过程是由用户或应用程序显式调用的, 而触发器是不能被直接调用的, 而是由一个事件来触发运行, 即触发器是当某个事件发生时自动地隐式运行。   具体而言, 触发器有如下作用:

  1. 可维护数据库的安全性、一致性和完整性。
  2. 可在写入数据表前,强制检验或转换数据。
  3. 当触发器发生错误时,异常的结果会被撤销。
  4. 部分数据库管理系统可以针对数据定义语言( DDL ) 使用触发器, 称为DDL 触发器, 还可以针对视图定义替代触发器(INSTEAD OF)。

  触发器的优点: 触发器可通过数据库中的相关表实现级联更改。从约束的角度而言, 触发器可以定义比CHECK 更为复杂的约束。与CHECK 约束不同的是, 触发器可以引用其他表中的列。例如, 触发器可以使用另一个表中的数据来比较更新的数据, 以及执行其他操作, 如修改数据或显示用户定义错误信息。触发器也可以评估数据修改前后的表的状态, 并根据其差异采取对策。一个表中的多个同类触发器(INSERT 、UPDATE 或DELETE) 允许采取多个不同的对策以响应同一个修改语句。   当然, 虽然触发器功能强大, 可以轻松可靠地实现许多复杂的功能, 但是它也具有一些缺点, 滥用会造成数据库及应用程序的维护困难。在数据库操作中, 可以通过关系、触发器、存储过程及应用程序等来实现数据操作。同时, 规则、约束、缺省值也是保证数据完整性的重要保障。如果对触发器过分依赖, 那么势必会影响数据库的结构, 同时增加了维护的复杂性。   对于触发器, 需要特别注意以下几点内容:

  1. 触发器在数据库里以独立的对象存储。
  2. 存储过程通过其他程序来启动运行或直接启动运行, 而触发器是由一个事件来启动运行。即触发器是当某个事件发生时自动地隐式运行。
  3. 触发器被事件触发。运行触发器称为触发或点火(FIRING) , 用户不能直接调用触发器。
  4. 触发器不能接收参数。  
12.视图

视图是由从数据库的基本表中选取出来的数据组成的逻辑窗口,它不同于基本表,它是一个虚拟表,其内容由查询定义。在数据库中,存放的是视图的定义而已,而不存放数据,这些数据仍然存放在原来的基本表结构当中。只有在使用视图的时候,才会执行视图的定义, 从基本表中查询数据。   同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表, 并且在引用视图时动态生成。对其中所引用的基础表而言, 视图的作用类似于筛选。定义视图可以来自当前或其他数据库的一个或多个表, 或者其他视图。分布式查询也可用于定义使用多个异类源数据的视图。如果有几台不同的服务器分别存储不同地区的数据, 那么当需要将这些服务器上相似结构的数据组合起来的时候,这种方式就非常有用。   通过视图进行查询没有任何限制, 用户可以将注意力集中在其关心的数据上, 而非全部数据, 这样就大大提高了运行效率与用户满意度。如果数据来源于多个基本表结构, 或者数据不仅来自于基本表结构,还有一部分数据来源于其他视图, 并且搜索条件又比较复杂, 需要编写的查询语句就会比较烦琐,此时定义视图就可以使数据的查询语句变得简单可行。定义视图可以将表与表之间的复杂的操作连接和搜索条件对用户不可见, 用户只需要简单地对一个视图进行查询即可, 所以,视图虽然增加了数据的安全性, 但是不能提高查询的效率。   视图看上去非常像数据库的物理表, 对它的操作同任何其他的表一样。当通过视图修改数据时,实际上是在改变基表( 即视图定义中涉及的表) 中的数据; 相反地, 基表数据的改变也会自动反映在由基表产生的视图中。由于逻辑上的原因, 有些OracIe 视图可以修改对应的基表, 有些则不能( 仅仅能查询) 。   数据库视图的作用有以下几点:

  1. 隐藏了数据的复杂性, 可以作为外模式, 提供了一定程度的逻辑独立性。
  2. 有利于控制用户对表中某些列或某些机密数据的访问, 提高了数据的安全性。
  3. 能够简化结构, 执行复杂查询操作。
  4. 使用户能以多种角度、更灵活地观察和共享同一数据。  
13. SQL注入

所谓SQL注入(SQL lnjection), 就是通过把SQL命令插入到WEB 表单提交或输入域名或页面请求的查询字符串, 最终达到欺骗服务器执行恶意的SQL 命令的目的。   例如, 在代码中使用下面的SQL语句:

SQL= "SELECT TOP 1 * FROM USER WHERE NAME=" +NAME + "AND PASSWORD=" +PASSWORD

来验证用户名和密码是否正确。 其中,NAME 和PASSWORD 是用户输入的内容。当用户输入用户名为AA , 密码为“ BB 或'A'='A' ” , 那么拼接出来的SQL语句就为

“ SELECT TOP 1 * FROM USER WHERE NAME='AA' AND PASSWORD='BB' OR 'A'='A' ”

那么只要USER 表中有数据, 这条SQL语句就会有返回结果。这就达到了SQL 注入的目的。   作为DBA,永远不要信任用户的输入,相反, 必须认定用户输入的数据永远都是不安全的, 对用户输入的数据必须都进行过滤处理。   为了防止SQL 注入, 需要注意以下几个要点:

  1. 永远不要信任用户的输入。可以通过正则表达式或限制长度的方式对用户的输入进行校验; 对单引号进行转换等。
  2. 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询、存取
  3. 永远不要使用管理员权限的数据库连接, 建议为每个应用赋予单独的权限。
  4. 不要把机密信息直接存放, 建议对密码或敏感信息进行加密或hash处理。
  5. 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装。
  6. SQL注入的检测一般采取辅助软件或借助网站平台, 软件一般采用SQL注入检测工具JSKY,网站平台就有亿思网站安全平台检测工具: MDCSOFT SCAN 等。采用MDCSOFT-IPS 可以有效地防御SQL 注入、XSS (Cross Site Scrpting , 跨站脚本攻击, 为了不和层叠样式表( Cascading Style Sheets , css)的缩写混淆, 故将跨站脚本攻击缩写为xss) 攻击等。
14. MVCC

在介绍MVCC 概念之前, 可以先来没想一下数据库系统里的一个问题: 在多用户的系统里, 假设有多个用户同时读写数据库里的一行记录, 那么怎么保证数据的一致性呢? 一个基本的解决方法是对这一行记录加上一把锁, 将不同用户对同一行记录的读写操作完全串行化执行, 由于同一时刻只有一个用户在操作, 因此一致性不存在问题。但是, 它存在明显的性能问题: 读会阻塞写, 写也会阻塞读, 整个数据库糸统的并发性能将大打折扣。   MVCC ( Multi-Vetsion Concurrent Control, 多版本并发控制) 广泛使用于数据库系统。MVCC 的目标是在保证数据一致性的前提下, 提供一种高并发的访问性能。在MVCC 协议中, 每个用户在连接数据库时看到的是一个具有一致性状态的镜像, 每个事务在提交到数据库之前对其他用户均是不可见的。当事务需要更新数据时, 不会直接覆盖以前的数据, 而是生成一个新的版本的数据, 因此一条数据会有多个版本存储, 但是同一时刻只有最新的版本号是有效的。因此, 读的时候就可以保证总是以当前时刻的版本的数据可以被读到, 不论这条数据后来是否被修改或删除。   可以将MVCC 看成行级锁的一种妥协, 它在许多情况下避免了使用锁, 同时可以提供更小的开销:根据实现的不同, 它可以允许非阻塞读, 在写操作进行时,只锁定需要的记录。MVCC 会保存某个时间点上的数据快照, 这意味着事务可以看到一个一致的数据视图, 而不管它们需要运行多久。这同时也意味着不同的事务在同一个时间点看到的同一个表的数据可能是不同的。   使用MVCC 多版本并发控制比锁定模型的主要优点是, 在MVCC 里, 对检索( 读) 数据的锁要求与写数据的锁要求不冲突, 所以, 读不会阻塞写,而写也从不阻塞读。在数据库里也有表和行级别的锁定机制, 用于给那些无法轻松接受MVCC行为的应用。不过, 恰当地使用MVCC 总会提供比锁更好的 性能。   大多数的MySQL 事务型存储引擎, 例如InnoDB 、Falcon 以及PBXT 都不使用简单的行锁机制, 它们都和MVCC 机制来一起使用。MVCC 不只使用在MySQL 中, Oracle 、PostgreSQL , 以及其他一些数据库系统也同样使用它。

15. 锁

锁( Lock) 机制用于管理对共享资源的并发访问, 用于多用户的环境下, 可以保证数据库的完整性和一致性。以商场的试衣间为例, 每个试衣间都可供多个消费者使用, 因此, 可能出现多个消费者同时需要使用试衣间试衣服。为了避免冲突, 试衣间装了锁, 某一个试衣服的人在试衣间里把锁锁住了, 其他顾客就不能再从外面打开了, 只能等待里面的顾客试完衣服, 从里面把锁打开, 外面的人才能进去。   当多个用户并发地存取数据时, 在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制, 则就有可能会读取和存储到不正确的数据, 破坏数据库的完整性和一致性。当事务在对某个数据对象进行操作前, 先向系统发出请求, 对其加锁。加锁后事务就对该数据对象有了一定的控制。

16. 更新丢失

更新丢失是指多个用户通过应用程序访问数据库时, 由于查询数据并返回到页面和用户修改完毕点击保存按钮将修改后的结果保存到数据库这个时间段( 即修改数据在页面上停留的时间) 在不同用户之间可能会存在偏差,从而最先查询数据并且最后提交数据的用户会把其他用户所作的修改覆盖掉。当两个或者多个事务选择同一行数据, 然后基于最初选定的值更新该行时, 会发生丢失更新问题。每个事务都不 知道其他事务的存在。最后的更新将重写由其他事务所、做的更新,这将导致数据丢失。   简单来说, 更新丢失就是两个事务都同时更新一行数据, 一个事务对数据的更新把另一个事务对数据的更新反映该掉了。这是因为系统没有执行任何的锁操作, 因此并发事务并没有被隔离开来。Serializable可以防止更新丢失的问题发生。其他的三个隔离级别都有可能发生更新丢失问题。Serializable 虽然可以防止更新丢失但是效率太低, 通常数据库不会用这个隔离级别, 所以, 需要其他的机制来防止更新丢失 例如悲观锁和乐观锁。

更新丢失可以分为以下两类:   第一类丢失更新: 在A 事务撤销时, 把己经提交的B 事务的更新数据覆盖了。这种错误可能造成很严重的问题,通过下面的账户取款转账就可以看出来:  

时间 取款事务A
T1 开始事务
T2 开始事务
T3 查询账户余额为1000元
T4 查询账户余额为1000元
T5 汇入100元把余额改为1100元
T6 提交事务
T7 取出100元把余额改为900元
T8 撤销事务
T9 余额恢复为1000元(丢失更新)

A事务在撤销时, “ 不小心” 将B 事务己经转入账户的金额给抹去了。

  第二类丢失更新: 在A事务提交时覆盖了B事务己经提交的数据, 造成B事务所做操作丢失:  

时间 取款事务A
T1 开始事务
T2 开始事务
T3 查询账户余额为1000元
T4 查询账户余额为1000元
T5 取出100元把余额改为900元
T6 提交事务
T7 汇入100元
T8 提交事务
T9 把余额改为1100元(更新丢失)
  上面的例子里由于支票转账事务覆盖了取款事务对存款余额所做的更新,导致银行最后损失了100元, 相反, 如果转账事务先提交, 那么用户账户将损失100 元。
17. 乐观锁和悲观锁

各种大型数据库所采用的锁的基本理论是一致的, 但在具体实现上各有差别。乐观锁和悲观锁不是数据库中真正存在的锁, 只是人们在解决更新丢失时的不同的解决方案, 体现的是人们看待事务的态度。   下表列出了悲观锁和乐观锁及其更新丢失的解决方案:  

名称 悲观锁(Pessimistic Lock) 乐观锁(Optimistic Lock)
描述 顾名思义,很悲观。每次去读数据的时候, 都认为别的事务会修改数据, 所以, 每次在读数据的时候都会上锁, 防止其他事务读取或修改这些数据, 这样导致其他事务会被阻塞, 直到这个事务结束 顾名思义, 很乐观。每次去拿数据的时候都认为别人不会修改, 所以, 不会上锁,但是在更新的时候会判断在此期间别人有没有去更新这个数据。乐观锁一般通过增加时间戳字段来实现。认为数据不会被其他用户修改, 所以,只需要修改屏幕上的信息而不需要锁
应用场景 数据更新比较频繁的场合 数据更新不频繁, 查询比较多的场合, 这样可以提高吞吐量
更新丢失解决方案 试图在更新之前把行锁住, 使用SELECT•••FOR UPDATE 然后更新数据 1. 使用版本列的乐观锁定增加NUMBER 或TIMES TAMP 或DATE 列, 通过增加一个时间戳列, 可以知道最后修改时间。每次修改行时, 检查数据库中这一列的值与最初读出的值是否匹配。若匹配的话则修改数据且通过触发器来负责递增NUMBER 、DATE 、TIMESTAMP 。 2. 使用校验和的乐观锁定用基数据本身来计算一个“ 虚拟的” 版本列, 生成散列值进行比较。数据库独立性好,从CPU使用和网络传输方面来看,资源开销量大。 3. 使用ORA_ROWSCN的乐观锁定建立在Oracle SCN 的基础上, 在建表时,需要启用ROWDEPENDENCIES , 防止整个数据块的ORA_ROWSCN向前推进。可以用SCN_TO_TIMESTAMP(ORA_ROWSCN) 将SCN转换为时间格式。将原先的悲观锁机制修改为乐观锁来控制并发,可以使用ORA_ROWSCN , 这样可以无须增加新列。也可以通过SCN_TO_TIMESTAMP来获取最后修改时间
 
18. 死锁

由于资源占用是互斥的, 当某几个进程提出申请对方进程占用的资源后, 相关进程在无外力协助下,永远分配不到对方进程申请的资源而无法继续运行, 这就产生了一种特殊现象:死锁。死锁是当程序中两个或多个进程发生永久阻塞(等待) 时, 而每个进程都在等待被其他进程占用并阻塞了的资源的一种数据库状态。例如, 如果进程A 锁住了记录1 并等待记录2 , 而进程B 锁住了录2 并等待记录1 ,那么这两个进程就发生了死锁。   在计算机系统中, 如果系统的资源分配策略不当, 更常见的可能是程序员写的程序有错误等情况下,那么会导致进程因竞争资源不当而产生死锁的现象。  

  1. 产生原因 ( 1 ) 系统资源不足。 ( 2 ) 进程运行推进的顺序不合适。 ( 3 ) 资源分配不当。 ( 4 ) 占用资源的程序崩溃等。 首先,如果系统资源充足,进程的资源请求都能够得到满足,那么死锁出现的可能性就很低, 否则,就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能会产生死锁。  
  2. 产生条件 ( 1 ) 互斥条件: 一个资源每次只能被一个进程使用。 ( 2 ) 请求与保持条件: 当一个进程因请求资源而被阻塞时, 对己获得的资源不会释放。 ( 3 ) 不可剥夺条件: 进程己获得的资源, 在末使用完之前, 不能强行被剥夺。 ( 4 ) 循环等待条件: 若干进程之间形成一种首尾相接的循环等待资源关系。 这四个条件是死锁的必要条件, 只要系统发生死锁, 这些条件必然成立, 而只要上述条件之一不满足,就不会发生死锁  
  3. 解决方法 理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大限度地避免、预防和解除死锁。所以, 在系统设计、进程调度等方面注意如何不让产生死锁的条件成立, 如何确定资源的合理分配算法,避免进程永久占据系统资源。此外, 也要防止进程在处于等待状态的情况下占用资源。在系统运行过程中, 对进程发出的每一个资源进行动态检查, 并根据检查结果决定是否分配资源, 若分配后系统可能发生死锁, 则不予分配, 否则予以分配。因此, 对资源的分配要给予合理的规划。
19.Redis

Redis是基于内存的, 因此对于内存是有非常高的要求,会把数据实时写到内存中,再定时同步到文件。Redis 可以当作数据库来使用, 但是有缺陷, 在可靠性上没有关系型数据库稳定。Redis 可作为持久层的Cache 层, 它可以缓存计数、排行榜样和队列( 订阅关系) 等数据库结构。   Redis的优点:

  • 完全居于内存,数据实时的读写内存,定时闪回到文件中, 性能极高, 读写速度快, Redis能支持超过100 K/s的读写频率。
  • 支持高并发, 官方宣传支持10 万级别的并发读写。
  • 支持机器重启后, 重新加载模式, 不会丢失数据。
  • 支持主从模式复制, 支持分布式。
  • 丰富的数据类型。Redis 支持Strings 、Lists 、Hashes 、Sets 及Ordered Sets数据类型。
  • 原子。Redis 的所有操作都是原子性的。
  • 丰富的特性。Redis 还支持Publish/Subscribe 等特性。
  • 开源。

  Redis的缺点:

  • 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写。
  • 没有原生的可扩展机制, 不具有自身可扩展能力, 要依赖客户端来实现分布式读写。
  • Redis 使用最佳方式是全部数据In-Memory。 虽然Redis 也提供持久化功能, 但实际更多的是一个disk-backed 功能, 跟传统意义上的持久化有比较大的区别。
  • 现在的Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。
  • 相比于关系型数据库, 由于其存储结构相对简单, 因此Redis 并不能对复杂的逻辑关系提供很好的支持。
  • Redis不支持复杂逻辑查询, 不适合大型项目要求。

  Redis适用场景:

  • 在非可靠数据存储中, 可以作为数据持久层或者数据缓存区。
  • 对于读写压力比较大、实时性要求比较高的场景下。
  • 关系型数据库不能胜任的场景( 如在SNS 订阅关系) 。
  • 订阅、发布系统。Pub/Sub 从字面上理解就是发布( Publish) 与订阅(Subscribe) , 在Redis 中,可以设定对某一个Key 值进行消息发布及消息订阅, 当一个Key 值上进行了消息发布后, 所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统, 例如普通的即时聊天、群聊等功能。
  • 事务( Transactions) 。虽然Redis 的Transactions 提供的并不是严格的ACID 的事务( 例如一串用EXEC 提交执行的命令, 如果在执行中服务器宕机, 那么会有一部分命令执行了, 剩下的没执行) , 但是这些Transactions 还是提供了基本的命令打包执行的功能( 在服务器不出问题的情况下, 可以保证一连串的命令是顺序在一起执行的) 。
20.select执行顺序

( 1 ) FROM 子句组装来自不同数据源的数据。 ( 2 ) WHERE 子句基于指定的条件对记录行进行筛选 ( 3 ) GROUP BY 子句将数据划分为多个分组。 ( 4 ) 使用聚集函数进行计算。 ( 5 ) 使用HAVING 子句筛选分组。 ( 6 ) 计算所有的表达式。 ( 7 ) SELECT 的字段。 ( 8 ) 使用ORDER BY 对结果集进行排序。

[^]:

[^]:

[^]:

[^]: