标签 触发器 下的文章

大家好,我是良许。

在嵌入式开发中,我们经常会接触到锁存器(Latch)、触发器(Flip-Flop)和寄存器(Register)这三个概念。

很多初学者容易把它们混淆,甚至认为它们是同一种东西。

实际上,虽然它们都是用于存储数据的数字电路元件,但在工作原理、应用场景和设计考量上存在着本质的区别。

今天我就来详细聊聊这三者的区别,帮助大家彻底理解它们。

1. 锁存器(Latch):电平触发的存储单元

1.1 基本工作原理

锁存器是最基础的存储单元,它的特点是电平触发

什么意思呢?

就是说只要使能信号(Enable)处于有效电平期间,锁存器的输出就会跟随输入变化。

一旦使能信号变为无效,锁存器就会"锁存"当前的数据状态,保持输出不变。

最常见的锁存器是 D 锁存器(Data Latch)。

当使能信号 EN 为高电平时,输出 Q 跟随输入 D 变化;当 EN 变为低电平时,Q 保持 EN 变为低电平前一刻 D 的值。

这就像一个透明的窗口,使能信号打开窗口时,数据可以自由通过;使能信号关闭窗口时,数据就被"锁"在里面了。

1.2 锁存器的问题

锁存器虽然结构简单,但在实际应用中存在一个严重的问题——透明性导致的不稳定

在使能信号有效期间,如果输入信号发生毛刺或者抖动,这些干扰都会直接传递到输出端,造成系统不稳定。

举个例子,假设我们在 STM32 中使用 GPIO 模拟一个锁存器的行为:

// 模拟锁存器行为(仅作演示,实际不推荐这样做)
uint8_t latch_data = 0;
uint8_t enable_signal = 0;
​
void Latch_Process(uint8_t input_data)
{
    if(enable_signal == 1)  // 使能信号有效
    {
        latch_data = input_data;  // 输出跟随输入
    }
    // 使能信号无效时,latch_data保持不变
}
​
// 在主循环中
while(1)
{
    enable_signal = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
    uint8_t input = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1);
    Latch_Process(input);
    
    // 只要enable_signal为高,input的任何变化都会立即反映到latch_data
}

在这段代码中可以看到,只要使能信号为高电平期间,输入的任何变化都会立即更新到锁存器中。

这在某些场景下是致命的,比如在数据传输过程中,如果使能信号持续时间过长,可能会采样到错误的中间状态。

1.3 锁存器的应用场景

尽管有这些问题,锁存器在某些特定场景下仍然有用武之地。

比如在地址锁存、总线保持、异步电路设计等场合。

在 8051 单片机中,就使用了地址锁存器来复用地址/数据总线。

另外,在 FPGA 设计中,有时为了降低资源消耗,也会在特定条件下使用锁存器。

2. 触发器(Flip-Flop):边沿触发的改进方案

2.1 触发器的核心改进

触发器是为了解决锁存器的透明性问题而设计的,它的核心特点是边沿触发

什么是边沿触发呢?

就是说触发器只在时钟信号的上升沿(或下降沿)这一瞬间采样输入数据,其他时间输入信号如何变化都不会影响输出。

最常用的是 D 触发器(D Flip-Flop)。

它在时钟信号的上升沿(或下降沿)时刻,将输入 D 的值传递到输出 Q,并保持到下一个时钟边沿到来。

这就像拍照一样,只在按下快门的瞬间捕捉画面,其他时间场景如何变化都不影响已经拍下的照片。

2.2 触发器的优势

边沿触发的特性使得触发器具有很强的抗干扰能力。

即使在时钟边沿之外的时间,输入信号有毛刺或抖动,也不会影响输出状态。

这使得触发器成为同步数字电路的基础单元。

我们可以用代码来模拟触发器的行为:

// 模拟D触发器行为
typedef struct {
    uint8_t Q;          // 输出
    uint8_t last_clk;   // 上一次的时钟状态
} DFlipFlop_t;
​
DFlipFlop_t dff = {0, 0};
​
void DFlipFlop_Process(uint8_t D, uint8_t clk)
{
    // 检测上升沿
    if(clk == 1 && dff.last_clk == 0)  // 上升沿触发
    {
        dff.Q = D;  // 只在上升沿采样输入
    }
    dff.last_clk = clk;  // 记录当前时钟状态
}
​
// 在定时器中断中使用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        static uint8_t clk_state = 0;
        clk_state = !clk_state;  // 生成时钟信号
        
        uint8_t input_data = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1);
        DFlipFlop_Process(input_data, clk_state);
        
        // 只有在时钟上升沿,input_data才会被采样到dff.Q
    }
}

这段代码展示了触发器只在时钟上升沿采样数据的特性。

即使在两个时钟边沿之间输入数据发生了多次变化,也只有上升沿那一刻的值会被捕获。

2.3 触发器的类型

除了 D 触发器,还有其他类型的触发器,比如 JK 触发器、T 触发器等。

但在现代数字设计中,D 触发器是最常用的,因为它的功能最直接、最容易理解和使用。

在 FPGA 和 ASIC 设计中,综合工具通常会将描述的时序逻辑综合成 D 触发器。

3. 寄存器(Register):多位数据的存储阵列

3.1 寄存器的本质

寄存器本质上是多个触发器的组合,用于存储多位二进制数据。

比如一个 8 位寄存器就是由 8 个 D 触发器并联组成的,它们共享同一个时钟信号,可以同时存储 8 位数据。

在嵌入式系统中,寄存器这个词的含义更加广泛。

我们经常说的"寄存器配置"、"寄存器映射",指的是处理器或外设内部的存储单元,用于控制硬件行为或存储状态信息。

3.2 寄存器的分类

在嵌入式开发中,我们接触到的寄存器主要有以下几类:

3.2.1 CPU 内部寄存器

这是 CPU 内部用于暂存数据和地址的高速存储单元。

比如 ARM Cortex-M 系列处理器有 R0-R15 这 16 个通用寄存器,还有程序状态寄存器 PSR、栈指针 SP 等特殊寄存器。

这些寄存器的访问速度最快,是 CPU 进行运算和数据传输的核心部件。

3.2.2 外设寄存器

这是用于配置和控制外设工作的寄存器。

在 STM32 中,每个外设都有一组寄存器,通过读写这些寄存器来控制外设的行为。

比如 GPIO 的配置寄存器、定时器的计数寄存器、UART 的数据寄存器等。

// STM32中配置GPIO的例子
void GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 使能GPIOA时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 配置PA5为输出模式
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 底层实际上是在配置GPIOA的多个寄存器:
    // MODER寄存器:设置引脚模式
    // OTYPER寄存器:设置输出类型
    // OSPEEDR寄存器:设置输出速度
    // PUPDR寄存器:设置上下拉
}
​
// 操作GPIO输出的例子
void LED_Toggle(void)
{
    // 读取当前输出状态
    GPIO_PinState state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5);
    
    // 翻转状态
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, !state);
    
    // 底层操作的是GPIOA的ODR(输出数据寄存器)
}

在这个例子中,HAL 库函数帮我们封装了底层的寄存器操作。

实际上,每个 GPIO 引脚的配置都对应着多个寄存器的特定位的设置。

这些寄存器就是由多个触发器组成的,用于存储 GPIO 的配置信息和状态。

3.2.3 移位寄存器

移位寄存器是一种特殊的寄存器,它不仅能存储数据,还能在时钟信号的控制下将数据左移或右移。

移位寄存器在串行通信、数据转换等场景中非常有用。

// 软件实现8位移位寄存器
typedef struct {
    uint8_t data;
} ShiftRegister_t;
​
ShiftRegister_t shift_reg = {0};
​
// 左移操作,新数据从右边进入
void ShiftRegister_LeftShift(uint8_t new_bit)
{
    shift_reg.data = (shift_reg.data << 1) | (new_bit & 0x01);
}
​
// 右移操作,新数据从左边进入
void ShiftRegister_RightShift(uint8_t new_bit)
{
    shift_reg.data = (shift_reg.data >> 1) | ((new_bit & 0x01) << 7);
}
​
// 使用示例:串行数据接收
void Serial_Receive_Bit(uint8_t bit)
{
    static uint8_t bit_count = 0;
    
    ShiftRegister_LeftShift(bit);  // 新位从右边移入
    bit_count++;
    
    if(bit_count == 8)  // 接收到完整的一个字节
    {
        uint8_t received_byte = shift_reg.data;
        // 处理接收到的字节
        Process_Received_Data(received_byte);
        bit_count = 0;
    }
}

这段代码展示了移位寄存器在串行数据接收中的应用。

每次接收到一个位,就将其移入寄存器,当接收满 8 位后,就得到了完整的一个字节。

3.3 寄存器的应用特点

寄存器在嵌入式系统中无处不在,它的主要特点包括:

  1. 存储容量:可以存储多位数据,从几位到几十位不等。
    CPU 内部的通用寄存器通常是 32 位或 64 位,外设寄存器根据功能需要可以是 8 位、16 位或 32 位。
  2. 访问速度:CPU 内部寄存器的访问速度最快,通常只需要一个时钟周期。
    外设寄存器的访问速度稍慢,但仍然远快于内存访问。
  3. 功能多样:不同的寄存器有不同的功能。
    有的用于数据存储,有的用于状态指示,有的用于控制配置,还有的具有特殊功能如自动清零、只读等特性。

4. 三者的对比总结

4.1 触发方式的差异

这是三者最核心的区别:

  • 锁存器:电平触发,使能信号有效期间输出跟随输入变化,透明传输。
  • 触发器:边沿触发,只在时钟边沿瞬间采样输入,其他时间输入变化不影响输出。
  • 寄存器:本质上是多个触发器的组合,也是边沿触发,但强调的是多位数据的存储功能。

4.2 稳定性对比

从稳定性角度来看:

锁存器由于透明性,容易受到输入毛刺的影响,在同步电路设计中通常不推荐使用。

触发器和寄存器由于边沿触发的特性,具有很好的抗干扰能力,是同步数字电路的基础。

在 FPGA 设计中,如果综合工具检测到代码会生成锁存器,通常会给出警告信息,因为这往往意味着设计存在问题。

比如在 Verilog 代码中,如果组合逻辑的条件分支不完整,就可能意外产生锁存器:

// 这段代码会产生锁存器(不推荐)
always @(*) begin
    if(enable)
        output_data = input_data;
    // 缺少else分支,当enable为0时,output_data保持不变
    // 这会被综合成锁存器
end
​
// 正确的写法(使用触发器)
always @(posedge clk) begin
    if(enable)
        output_data <= input_data;
    // 即使没有else,在时钟边沿output_data也会保持上一个值
    // 这会被综合成触发器
end

4.3 应用场景对比

在实际应用中:

锁存器主要用于异步电路、地址锁存、总线保持等特殊场景。

在现代同步数字设计中使用较少。

触发器是同步数字电路的基本单元,用于构建状态机、计数器、时序控制等各种时序逻辑。

寄存器应用最为广泛,几乎存在于数字系统的每个角落。

在嵌入式开发中,我们配置硬件、读取状态、传输数据,都离不开寄存器操作。

4.4 在嵌入式开发中的实践

在实际的嵌入式开发中,我们很少直接设计锁存器或触发器电路,这些都是芯片内部已经实现好的。

我们更多的是通过操作寄存器来控制硬件行为。

但理解它们的工作原理,对于理解硬件时序、调试时序问题、优化代码性能都非常有帮助。

比如在编写中断服务程序时,我们需要清除中断标志位,这实际上就是在操作状态寄存器:

// UART中断服务函数
void USART1_IRQHandler(void)
{
    // 检查接收中断标志
    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))
    {
        // 读取接收到的数据(读取DR寄存器会自动清除RXNE标志)
        uint8_t received_data = (uint8_t)(huart1.Instance->DR & 0xFF);
        
        // 处理接收到的数据
        Process_UART_Data(received_data);
    }
    
    // 检查发送完成中断标志
    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC))
    {
        // 清除发送完成标志(写1清零)
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TC);
        
        // 处理发送完成事件
        Handle_Transmit_Complete();
    }
}

在这个例子中,中断标志位就存储在 UART 的状态寄存器中。

这个寄存器由多个触发器组成,每个触发器存储一个标志位。

当硬件事件发生时,相应的触发器被置位;当我们读取数据或写入清零命令时,触发器被复位。

5. 总结

锁存器、触发器和寄存器是数字电路中三个层次递进的概念。

锁存器是最基础的存储单元,但由于电平触发的特性导致稳定性问题。

触发器通过边沿触发解决了锁存器的问题,成为同步电路的基础。

寄存器则是多个触发器的组合,用于存储多位数据,在嵌入式系统中应用最为广泛。

理解这三者的区别,不仅有助于我们更好地理解硬件工作原理,也能帮助我们在编写代码时更加注意时序问题,写出更加稳定可靠的程序。

在嵌入式开发中,虽然我们主要是通过操作寄存器来控制硬件,但了解底层的触发器和锁存器原理,能让我们对硬件行为有更深入的认识,在遇到复杂的时序问题时也能更快地定位和解决。

希望这篇文章能帮助大家彻底理解锁存器、触发器和寄存器的区别。

如果你在实际开发中遇到相关问题,欢迎交流讨论。

更多编程学习资源

金仓数据库中包含各种数据库对象,常见的KingBase对象有:数据库、模式、表、索引、视图、存储过程、存储函数和触发器等等。这里将介绍金仓数据库中常见的数据库对象以及如何使用它们。视频讲解如下:
https://www.bilibili.com/video/BV1RQz3B9ERT/?aid=115930646454...

一、 数据库与模式

数据库本身也是一个KingBase的数据库对象。数据库对象中包含其他所有的数据库对象,如:模式、表、视图、索引等等。使用命令create database可以创建一个新的数据库,下面展示了该命令的格式:

CREATE DATABASE name
     [ WITH ] [ OWNER [=] user_name ]
           [ TEMPLATE [=] template ]
           [ ENCODING [=] encoding ]
           [ LC_COLLATE [=] lc_collate ]
           [ LC_CTYPE [=] lc_ctype ]
           [ TABLESPACE [=] tablespace_name ]
           [ ALLOW_CONNECTIONS [=] allowconn ]
           [ CONNECTION LIMIT [=] connlimit ]
           [ IS_TEMPLATE [=] istemplate ]

一个数据库包含一个或多个模式(Schema),模式中又包含了表、函数及操作符等数据库对象。创建新数据库时,KingBase会自动创建名为public的模式。使用命令create schema可以创建一个新的模式,下面展示了该命令的格式:

CREATE SCHEMA schema_name [ AUTHORIZATION role_specification ] [ schema_element [ ... ] ]
CREATE SCHEMA AUTHORIZATION role_specification [ schema_element [ ... ] ]
CREATE SCHEMA IF NOT EXISTS schema_name [ AUTHORIZATION role_specification ]
CREATE SCHEMA IF NOT EXISTS AUTHORIZATION role_specification

其中 role_specification 可以是:

    user_name
  | CURRENT_USER
  | SESSION_USER

在了解到数据库与模式的概念后,下面通过具体的操作来演示如何创建和使用它们。
(1)创建一个新的数据库dbtest。

scott=# create database dbtest;

(2)查看已存在的数据库列表。

scott=# \l

# 输出的信息如下:
                                        数据库列表
   名称    | 拥有者 | 字元编码 |  校对规则   |    Ctype    | ICU 排序 |     存取权限      
-----------+--------+----------+-------------+-------------+----------+-------------------
 dbtest    | system | UTF8     | zh_CN.UTF-8 | zh_CN.UTF-8 |          | 
 kingbase  | system | UTF8     | zh_CN.UTF-8 | zh_CN.UTF-8 |          | 
 scott     | system | UTF8     | zh_CN.UTF-8 | zh_CN.UTF-8 |          | 
 security  | system | UTF8     | zh_CN.UTF-8 | zh_CN.UTF-8 |          | 
 template0 | system | UTF8     | zh_CN.UTF-8 | zh_CN.UTF-8 |          | =c/system        +
           |        |          |             |             |          | system=CTc/system
 template1 | system | UTF8     | zh_CN.UTF-8 | zh_CN.UTF-8 |          | =c/system        +
           |        |          |             |             |          | system=CTc/system
 test      | system | UTF8     | zh_CN.UTF-8 | zh_CN.UTF-8 |          | 
(7 行记录)

(3)切换到数据库dbtest。

scott=# \c dbtest 
您现在以用户名"system"连接到数据库"dbtest"。

(4)查看数据库dbtest中的模式。

dbtest=# \dn

# 输出的信息如下:
       架构模式列表
       名称       | 拥有者 
------------------+--------
 anon             | system
 dbms_job         | system
 dbms_scheduler   | system
 dbms_sql         | system
 kdb_schedule     | system
 perf             | system
 public           | system
 src_restrict     | system
 sys_hm           | system
 sysaudit         | system
 sysmac           | system
 wmsys            | system
 xlog_record_read | system
(13 行记录)

# 这里的public的模式是创建数据库对象的默认模式。

(5)创建一个新的模式。

dbtest=# create schema firstschema;

(6)重新查看数据库dbtest中的模式。

dbtest=# \dn

# 输出的信息如下:
       架构模式列表
       名称       | 拥有者 
------------------+--------
 anon             | system
 dbms_job         | system
 dbms_scheduler   | system
 dbms_sql         | system
 firstschema      | system
 kdb_schedule     | system
 perf             | system
 public           | system
 src_restrict     | system
 sys_hm           | system
 sysaudit         | system
 sysmac           | system
 wmsys            | system
 xlog_record_read | system
(14 行记录)

二、 创建与管理表

表是一种非常重要的数据库对象。金仓数据库的数据都是存储在表中。KingBase的表是一种二维结构,由行和列组成。表有列组成,列有列的数据类型。下面通过具体的步骤来演示如何操作金仓数据库的表。这些操作包括创建表、查看表、修改表和删除表。

(1)创建一张新的表test2.

dbtest=# create table test2(id int,name varchar(32),age int);

# 由于创建表时没有指定模式的名称,因此表将创建在public模式下。
# 如果要在指定的模式下创建表,可以使用下面的语句:
dbtest=# create table firstschema.test2(id int,name varchar(32),age int);

(2)查看表的结构。

dbtest=# \d test2

# 输出的信息如下:
                    数据表 "public.test2"
 栏位 |            类型            | 校对规则 | 可空的 | 预设 
------+----------------------------+----------+--------+------
 id   | integer                    |          |        | 
 name | character varying(32 char) |          |        | 
 age  | integer                    |          |        | 

(3)在表中增加一个字段。

dbtest=# alter table test2 add gender varchar(1) default 'M';

# 这里增加了一个gender字段用于表示性别,默认是“M”。

(4)重新查看表的结构。

dbtest=# \d test2

# 输出的信息如下:
                         数据表 "public.test2"
  栏位  |            类型            | 校对规则 | 可空的 |     预设     
--------+----------------------------+----------+--------+--------------
 id     | integer                    |          |        | 
 name   | character varying(32 char) |          |        | 
 age    | integer                    |          |        | 
 gender | character varying(1 char)  |          |        | 'M'::varchar

(5)修改表将gender字段的长度改为10个字符。

dbtest=# alter table test2 alter gender type varchar(10);

(6)删除gender字段。

dbtest=# alter table test2 drop column gender;

(7)删除表test2。

dbtest=# drop table test2;

三、 在查询时使用索引

数据库查询是数据库的主要功能之一,最基本的查询算法是顺序查找(linear search)时间复杂度为O(n),显然在数据量很大时效率很低。优化的查找算法如二分查找(binary search)、二叉树查找(binary tree search)等,虽然查找效率提高了。但是各自对检索的数据都有要求:二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构。所以在数据之外,数据库系统还维护着满足特定查找算法的数据结构。这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构就是索引。金仓数据库官方对索引的定义为:索引(Index)是帮助KingBase高效获取数据的数据结构。索引是一种数据结构。金仓数据库默认的索引类型是B树索引。下图是一颗简单的B树,可见它与二叉树最大的区别是它允许一个节点有多于2个的元素,每个节点都包含key和数据,查找时可以使用二分的方式快速搜索数据。

image.png

在了解到了KingBase索引的基本知识以后,下面将通过具体的步骤演示来说明如何在KingBase中创建索引,并且在查询语句中使用它。
(1)查看scott数据库中部门表dept和员工表emp上的索引信息。

scott=# select index_name,index_type,table_name,status
        from user_indexes where table_name in ('DEPT','EMP');
        
# 输出的信息如下:
 index_name | index_type | table_name | status 
------------+------------+------------+--------
 DEPT_PKEY  | BTREE      | DEPT       | VALID
 EMP_PKEY   | BTREE      | EMP        | VALID
(2 行记录)

# user_indexes是一个视图,可以通过它获取某个用户创建的索引信息。

(2)使用create index命令在员工表emp的薪水sal字段上创建完全索引。

scott=# create index index_full on emp using btree(sal);

# 完全索引会基于该字段上的所有值创建索引。
# 同时,在创建索引的时候会进行锁表的操作,可以使用 CIC (create index concurrently),
# 但创建索引的时间相对较长。例如:
scott=# create index concurrently index1 on emp using btree(sal);

(3)下面的语句将在员工表上创建一个部分索引。

scott=# create index index_part on emp using btree(sal) where sal<3000;

# 部分索引是对于表的部分数据创建索引。
# 如果发现表的某一部分数据查询次数较多时,可以考虑在这部分数据上创建一个部分索引。
# 部分索引相较于完全索引,查询的性能将得到提高,并且部分索引文件所占的空间也会小于全索引。

(4)在员工表emp的员工姓名ename上创建表达式索引。

scott=# create index index_exp on emp(lower(ename));

# 对于表达式索引的维护代价比较高,因为在每一行插入或更新时需要重新计算相应表达式的值,
# 但是针对于表达式索引在查询时的效率更高,因为表达式的值会直接存储在索引中。

(5)使用explain语句查看SQL查询时的执行计划。

scott=# explain select * from emp where lower(ename) like 'king';

# 输出的信息如下:
                     QUERY PLAN                     
----------------------------------------------------
 Seq Scan on emp  (cost=0.00..1.21 rows=1 width=42)
   Filter: (lower((ename)::text) ~~ 'king'::text)
(2 行记录)

# 从输出的执行计划可以看出,此时并没有使用到表达式索引。
# 这是由于KingBase并不能强制使用特定的索引,或者完全阻止KingBase进行Seq Scan的顺序扫描。
# 但可以通过将参数enable_seqscan设置为 off的方式让KingBase尽可能避免执行某些扫描类型,
# 但这样的方式多用于开发和调试中。

(6)禁止金仓数据库使用顺序扫描。

scott=# set enable_seqscan = off;

(7)重新使用explain语句查看SQL查询时的执行计划。

scott=# explain select * from emp where lower(ename) like 'king';

# 输出的信息如下:
                              QUERY PLAN                              
----------------------------------------------------------------------
 Index Scan using index_exp on emp  (cost=0.14..8.16 rows=1 width=42)
   Index Cond: (lower((ename)::text) = 'king'::text)
   Filter: (lower((ename)::text) ~~ 'king'::text)
(3 行记录)

四、 使用视图简化查询语句

当SQL的查询语句比较复杂并且需要反复执行,如果每次都重新书写该SQL语句显然不是很方便。因此金仓数据库数据库提供了视图用于简化复杂的SQL语句。视图(View)是一种虚表,其本身并不包含数据。它将作为一个select语句保存在数据字典中的。视图依赖的表叫做基表。通过视图可以展现基表的部分数据;视图数据来自定义视图的查询中使用的基表。在金仓数据库中创建视图的基本语法格式如下:

CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] [ RECURSIVE ] [ FORCE ] VIEW name [ ( column_name [, ...] ) ]
    [ WITH ( view_option_name [= view_option_value] [, ... ] ) ]
    [ BEQUEATH { CURRENT_USER | DEFINER } ]
    AS query
    [ WITH { [ CASCADED | LOCAL ] CHECK OPTION } | READ ONLY ]

在了解的视图的作用后,下面通过具体的步骤来演示如何使用视图。
(1)基于员工表emp创建视图。

scott=# create or replace view view1
as
select * from emp where deptno=10;

# 视图也可以基于多表进行创建,例如:
scott=# create or replace view view2
as
select emp.ename,emp.sal,dept.dname
from emp,dept
where emp.deptno=dept.deptno;

(2)查看视图view2的结构。

scott=# \d view2

# 输出的信息如下:
                      视图 "public.view2"
 栏位  |            类型            | 校对规则 | 可空的 | 预设 
-------+----------------------------+----------+--------+------
 ename | character varying(10 char) |          |        | 
 sal   | integer                    |          |        | 
 dname | character varying(10 char) |          |        | 

(3)从视图中查询数据。

scott=# select * from view2;

# 输出的信息如下:
 ename  | sal  |   dname    
--------+------+------------
 MILLER | 1300 | ACCOUNTING
 CLARK  | 2450 | ACCOUNTING
 KING   | 5000 | ACCOUNTING
 SCOTT  | 3000 | RESEARCH
 JONES  | 2975 | RESEARCH
 SMITH  |  800 | RESEARCH
 ADAMS  | 1100 | RESEARCH
 FORD   | 3000 | RESEARCH
 WARD   | 1250 | SALES
 TURNER | 1500 | SALES
 ALLEN  | 1600 | SALES
 BLAKE  | 2850 | SALES
 MARTIN | 1250 | SALES
 JAMES  |  950 | SALES
(14 行记录)

(4)通过视图执行DML操作,例如:给10号部门员工涨100块钱工资。

scott=# update view1 set sal=sal+100;

# 并不是所有的视图都可以执行DML操作。在视图定义时含义以下内容,视图则不能执行DML操作:
# 1.  查询子句中包含distinct和组函数
# 2.  查询语句中包含group by子句和order by子句
# 3.  查询语句中包含union 、union all等集合运算符
# 4.  where子句中包含相关子查询
# 5.  from子句中包含多个表
# 6.  如果视图中有计算列,则不能执行update操作
# 7.  如果基表中有某个具有非空约束的列未出现在视图定义中,则不能做insert操作

(5)创建视图时使用WITH CHECK OPTION约束 。

scott=# create or replace view view3
as
select * from emp where sal<1000
with check option;

# WITH CHECK OPTION表示对视图所做的DML操作,不能违反视图的WHERE条件的限制。

(6)在view3上执行update操作。

scott=# update view3 set sal=2000;

# 此时将出现下面的错误信息:
# ERROR:  新行违反了视图"view3"的检查选项
# DETAIL:  失败, 行包含(7369, SMITH, CLERK, 7902, 1980/12/17, 2000, null, 20).