如何在 PostgreSQL 中使用物化视图

一、关于视图和实例化视图

对于复杂的 SQL 查询,每次需要其结果时重写整个查询是不切实际的。视图解决了这个问题。视图是命名(预定义)查询和包含该查询输出的伪表。基于查询创建视图的代码如下所示:

-- pseudocode

CREATE VIEW my_view AS

    SELECT ...

        FROM ... JOIN ... ON ...

        WHERE ... AND ... 

        ORDER BY ...

        LIMIT ...

实质上,查询以 auto 开头。这将创建一个新视图;像查询常规表一样查询它:CREATE VIEW my_view ASmy_view

-- pseudocode

SELECT ...  

    FROM my_view 

    WHERE ...

因此,您可以仅使用视图访问查询结果,而无需写出整个查询。视图部分类似于编程语言中的“函数”——复杂查询的简称。它有助于用户体验和代码的可读性。

但在表面之下,每次访问视图时,数据库都会将视图转换为完整查询,并在呈现输出之前(重新)评估它。每次重新计算复杂查询效率低下,并且不会提高数据库的性能。相反,在大型表上重复执行复杂查询会降低性能。

与视图一样,实例化视图为数据库提供了一致的接口。物化视图抽象出数据库设计和实现细节,为 API 层提供一致的查询接口。实例化视图将查询结果缓存在持久结构中,因此无需重新计算即可访问查询结果。这样可以节省重复访问的复杂查询的时间。

实例化视图通过在类似于表的持久数据结构中存储(缓存)命名(预定义)查询的输出来解决此问题。您可以运行查询并在其上创建索引,就好像它们是常规表一样。还可以基于对其他实例化视图的查询来构造实例化视图。SELECT

具体化视图在PostgreSQL,Oracle数据库,SQL Server和其他一些数据库引擎中可用。此功能在 MySQL 上不可用。

1.1、准备工作

要从本指南中受益,必须对PostgreSQL数据库有基本的了解。假设您有一些 PostgreSQL 基础知识的经验 – 安装软件、创建新数据库、创建表、标准查询等。对于 SQL 示例,假设您已经在Ubuntu、FreeBSD、CentOS或其后续产品上设置了一个正在运行的 PostgreSQL 实例,或者您正在使用托管数据库服务。

本指南中的 SQL 示例在运行在 FreeBSD 13.1-RELEASE 上的 PostgreSQL 14.5 上进行了测试。它们应该与在所有最近的操作系统上运行的所有最新版本的PostgreSQL兼容。

二、设置测试表

在创建实例化视图之前,请设置两个测试表并用数据填充它们。

创建一个表:product

CREATE TABLE IF NOT EXISTS product (

    product_id INTEGER PRIMARY KEY, 

    name VARCHAR(20) NOT NULL, 

    price SMALLINT NOT NULL

);

创建一个表:orders

CREATE TABLE IF NOT EXISTS orders (

    order_id INTEGER, 

    product_id INTEGER REFERENCES product (product_id),

    PRIMARY KEY (order_id, product_id)

);

检查已创建表的说明:

\d orders

将一些包含虚拟数据的行插入到表中:product

 INSERT INTO product (product_id, name, price) VALUES (1, 'Floppy Disk Drive', 40);

 INSERT INTO product (product_id, name, price) VALUES (2, 'Oculus Quest', 400);

将虚拟数据插入表中:orders

INSERT INTO orders (order_id, product_id) VALUES (1, 1);

INSERT INTO orders (order_id, product_id) VALUES (1, 2);

INSERT INTO orders (order_id, product_id) VALUES (2, 1);

检查表中的数据:

SELECT * FROM orders;



SELECT * FROM product;

 三、创建实例化视图

在联接查询上创建实例化视图:

CREATE MATERIALIZED VIEW mv_products_orders

AS

SELECT 

    p.product_id, 

    o.order_id, 

    p.name, 

    p.price 

FROM 

    product p 

JOIN 

    orders o 

ON 

    p.product_id = o.product_id;

这将创建一个实例化视图,并根据创建实例化视图时基础表中的数据填充该视图。默认情况下,实例化视图的列名派生自基础表的列名。mv_products_orders

检查新创建的实例化视图的定义:

\d mv_products_orders

在以下位置签入数据:mv_products_orders

SELECT * FROM mv_products_orders;

要重命名实例化视图,请使用以下命令:ALTER

ALTER MATERIALIZED VIEW mv_products_orders RENAME TO my_mv;

 四、在实例化视图上创建索引

实例化视图上的索引与常规表上的索引具有相同的优势 – 它们有助于快速查找。特别是,刷新(在后面的部分中讨论)实例化视图的常见方法需要使用索引。可以在实例化视图的任何列上定义索引。

CREATE UNIQUE INDEX product_order ON mv_products_orders (order_id, product_id);

检查实例化视图的描述现在是否包含索引:

\d mv_products_orders

 五、(具体化)(实例化)视图的视图

可以基于其他实例化视图构造实例化视图。

CREATE MATERIALIZED VIEW my_mv

AS

SELECT 

    * from mv_products_orders

    limit 2;

同样,也可以基于实例化视图创建视图和基于视图创建实例化视图。

六、刷新(更新)实例化视图

PostgreSQL 不会自动刷新物化视图。这意味着,默认情况下,实例化视图中的数据在更新基础表时会过时。您需要手动更新实例化视图或将系统配置为自动更新。

向表添加新行:orders

INSERT INTO orders (order_id, product_id) VALUES (2, 2);

重新检查实例化视图:

SELECT * FROM mv_products_orders;

输出仍然与以前相同。

七、手动刷新

该命令用于刷新实例化视图的内容:REFRESH

REFRESH MATERIALIZED VIEW mv_products_orders;

检查实例化视图现在是否包含新添加到表中的数据:orders

SELECT * FROM mv_products_orders;

执行刷新将放弃旧内容并重新创建实例化视图。请注意,在以这种方式刷新实例化视图时,无法查询实例化视图。刷新操作会锁定实例化视图,并阻止对其的偶数查询。此锁一直保持到(刷新)事务结束。SELECT

7.1、该选项CONCURRENTLY

使用该选项刷新可解决此问题。CONCURRENTLY

REFRESH MATERIALIZED VIEW CONCURRENTLY mv_products_orders;

使用该选项,数据库在刷新实例化视图时不会阻止对实例化视图的查询。指定此选项时,它将在内部使用实例化视图查询的新结果创建一个临时数据结构。比较新旧结果,并使用 and 操作将更改应用于原始实例化视图。CONCURRENTLYSELECTUPDATEINSERT

请注意,为了同时刷新,实例化视图必须至少包含一个基于列的唯一索引。当您尝试在没有唯一索引的实例化视图上使用它时,它会抛出错误:

ERROR:  cannot refresh materialized view "public.mv_products_orders" concurrently

HINT:  Create a unique index with no WHERE clause on one or more columns of the materialized view. 

另请注意,一次只能在实例化视图上运行一个刷新操作(即使使用此选项也是如此)。CONCURRENTLY

7.2、折衷

如果更新(刷新)涉及大量新数据,则不使用选项时刷新速度更快。这是因为并发刷新中涉及的所有比较和更新操作。CONCURRENTLY

实用提示:通常,定期清空数据库以清理未使用的数据结构并释放空间会很有帮助。这与并发刷新尤其相关,因为此操作涉及创建临时数据结构。建议在刷新后进行吸尘。吸尘本身就是一个广泛的主题,超出了本指南的范围。

八、自动刷新

截至 2022 年 11 月,PostgreSQL 没有自动刷新物化视图的功能。但是,可以使用其他工具设置自动刷新。

8.1、cron 工作

自动刷新实例化视图的常用方法是使用 cron 作业:

15 * * * * psql -d name_of_your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY mv_products_orders"

将此行添加到 用户的 crontab 中将每 15 分钟调用一次命令,并将数据库的名称和 SQL 命令作为参数传递给它以刷新物化视图。postgrespsql

对于上面的命令,请注意,如果您没有显式创建或连接到特定数据库,则默认情况下,查询将在数据库中执行。psqlpostgres

实用提示:由于一次只能运行一个刷新操作,因此在为其安排 cron 作业之前,了解刷新操作需要多长时间非常重要。

6.2、触发器

也可以使用触发器来更新实例化视图。为此,请创建一个刷新实例化视图的函数。在数据进入实例化视图的那些表上,设置触发器以在 、 和操作之后调用此函数。INSERTUPDATEDELETE

创建一个 PL/pgSQL – 刷新物化视图的 SQL 过程语言函数:

CREATE OR REPLACE FUNCTION mv_refresh()

RETURNS trigger LANGUAGE plpgsql AS $$

BEGIN

    REFRESH MATERIALIZED VIEW CONCURRENTLY mv_products_orders;

    RETURN NULL;

END;

$$;

创建一个触发器,该触发器在表上运行某些操作(,和)时调用此函数:INSERTUPDATEDELETEorders

CREATE TRIGGER mv_trigger 

AFTER INSERT OR UPDATE OR DELETE

ON orders

FOR EACH STATEMENT EXECUTE PROCEDURE mv_refresh();

检查表的定义是否包含触发器:orders

\d orders

同样,添加一个触发器以在表中的数据更改时调用函数。mv_refresh()products

从订单表中删除一行:

DELETE FROM orders WHERE order_id = 1 AND product_id = 2;

检查实例化视图是否不再包含已删除的行:

SELECT * FROM mv_products_orders;

 七、删除实例化视图

删除实例化视图类似于删除常规视图。

DROP MATERIALIZED VIEW mv_products_orders;

要删除实例化视图以及依赖于它的所有其他对象,请使用以下选项:CASCADE

DROP MATERIALIZED VIEW IF EXISTS mv_products_orders CASCADE;

上面的命令删除了实例化视图:,以及基于它创建的视图。mv_products_ordersmy_mv

八、结论

与所有优化工具一样,物化视图的使用涉及权衡。在决定工具是否合适之前,了解每个用例的特定需求非常重要。

8.1、成本

实例化视图会消耗额外的存储空间,但实际上,当存储便宜时,额外存储的成本并不是决定性因素。此外,还要考虑数据新近度;如果基础表经常更新,则实例化视图缓存的数据在使用时可能已部分过时。对于需要返回实时数据的查询来说,这是一个问题。自动刷新有助于提高数据新近度,但它们的使用需要权衡取舍,尤其是对于大型表和写入密集型数据库。

8.2、用例

具体化视图有助于提高性能,在系统需要处理大量相同(事先已知)复杂查询的情况下,通常可以显著提高性能。例如:

  • 涉及对大型表进行复杂查询的报告和分析应用程序
  • 涉及非结构化或半结构化数据的数据库设计,其中查询效率低下
  • 显示整理或合并(每日、每月等)信息的仪表板
  • 涉及外部表和数据存储的查询 – 重复查询数据源可能很慢或成本高昂
  • 向第三方提供 API 服务,其中合同要求通常需要一致的 API 结构,并且预计负载较重

当基础查询简单或快速时,不需要使用实例化视图。

具体化视图不适用于支持实时应用程序(如实时交易、在线竞价、体育比分、消息传递、实时新闻源等)的查询。

如何在 Ubuntu 20.04 上使用 Python 实现 PostgreSQL 数据库事务

一、介绍

数据库事务是实现业务逻辑的 SQL 命令链。例如,在电子商务应用程序中,填写客户订单所需的 SQL 命令可能会影响 、 和 表。数据库事务解决了原子性原则,该原则指出事务在数据库中应具有全有或全无影响。如果事务中的任何 SQL 命令失败,数据库应删除(回滚)整个事务。PostgreSQL 是最受欢迎的数据库服务器之一,它支持事务以消除部分数据库更新的可能性。sales_orderssales_order_productssales_payments

本指南向您展示如何使用 PostgreSQL 实现 PostgreSQL 事务,这是一个用于连接到 PostgreSQL 服务器的高级 Python 库。psycopg2

二、准备工作

要完成本指南,请执行以下操作:

  • 部署 Ubuntu 20.04 服务器。
  • 创建一个非根 sudo 用户。
  • 预配托管的 PostgreSQL 数据库群集。
  • 找到 PostgreSQL 数据库集群的连接详细信息,位于“概述”选项卡下。本指南使用以下示例连接详细信息:
    • 用户名vultradmin
    • 密码EXAMPLE_POSTGRESQL_PASSWORD
    • 主持人SAMPLE_POSTGRESQL_DB_HOST_STRING.vultrdb.com
    • 端口16751

三、设置示例数据库

此示例数据库是存储客户及其贷款余额的银行应用程序的后端。

此应用程序使用两个表来完成数据库事务。该表存储客户的名称。然后,该表存储客户的贷款余额。稍后,本指南将介绍如何使用 Linux 命令将示例事务发送到应用程序。应用程序必须将事务作为单个工作单元完成,才能完成业务逻辑。否则,数据库应拒绝部分完成的事务。customersloanscurl

若要设置此示例应用程序,需要包连接到托管 PostgreSQL 数据库群集并创建数据库。按照以下步骤安装软件包并初始化数据库:postgresql-client

  1. 更新服务器的软件包信息索引。
    $ sudo apt update 
  2. 使用该工具安装包。aptpostgresql-client
    $ sudo apt install -y postgresql-client
  3. 登录到托管的 PostgreSQL 数据库集群。替换为适用于您的数据库的正确版本。SAMPLE_POSTGRESQL_DB_HOST_STRING.vultrdb.comhost
    $ psql -h SAMPLE_POSTGRESQL_DB_HOST_STRING.vultrdb.com -p 16751 -U vultradmin defaultdb

    输出:

    Password for user vultradmin:
  4. 输入托管 PostgreSQL 数据库集群的密码,然后按继续。ENTER输出:
    defaultdb=>
  5. 发出以下 SQL 命令以创建示例数据库。bank_db
    defaultdb=> CREATE DATABASE bank_db;

    输出:

    CREATE DATABASE
  6. 连接到新数据库。bank_db
    defaultdb=> \c bank_db;

    输出:

    ...
    
    You are now connected to database "bank_db" as user "vultradmin".
  7. 创建示例表。此表存储 、 和 。该关键字指示 PostgreSQL 服务器自动生成新的。customerscustomer_idsfirst_nameslast_namesSERIALcustomer_ids
    bank_db=> CREATE TABLE customers (
    
                  customer_id SERIAL PRIMARY KEY,
    
                  first_name VARCHAR(50),
    
                  last_name VARCHAR(50)        
    
              );

    输出:

    CREATE TABLE
  8. 创建表。此表存储客户持有的贷款帐户余额。此表中的列链接回表中的同一列。loanscustomer_idcustomers
    bank_db=> CREATE TABLE loans (
    
                  loan_id SERIAL PRIMARY KEY,
    
                  customer_id BIGINT,
    
                  amount DECIMAL(17, 4)  
    
              );

    输出:

    CREATE TABLE
  9. 从托管的 PostgreSQL 数据库集群注销。
    bank_db=> \q
  10. 按照下一步创建一个数据库类来访问您的示例 PostgreSQL 数据库。

四、 创建自定义的 PostgreSQL 数据库类

设置示例数据库后,现在需要一个连接到数据库的中心类来存储表中的数据。按照以下步骤创建类:

  1. 首先创建一个新目录,将源代码与系统文件分开。project
    $ mkdir project
  2. 导航到新目录。project
    $ cd project
  3. 在文本编辑器上打开一个新文件。postgresql_db.py
    $ nano postgresql_db.py
  4. 在文件中输入以下信息。请记住将数据库凭据(、、和)替换为 PostgreSQL 数据库集群的正确值。postgresql_db.pydb_hostdb_userdb_passdb_port
    import psycopg2
    
    
    
    class PostgresqlDb:
    
    
    
        def __init__(self):
    
    
    
            db_host = 'SAMPLE_POSTGRESQL_DB_HOST_STRING.vultrdb.com' 
    
            db_name = 'bank_db'  
    
            db_user = 'vultradmin'
    
            db_pass = 'EXAMPLE_POSTGRESQL_PASSWORD'   
    
            db_port = 16751
    
    
    
            self.db_conn = psycopg2.connect(host = db_host, database = db_name, user = db_user, password = db_pass, port = db_port)
    
    
    
        def execute_db(self, json_payload):
    
    
    
            try: 
    
    
    
                print("Starting new database transaction...")
    
    
    
                self.db_conn.autocommit = False
    
    
    
                self.cur = self.db_conn.cursor()   
    
    
    
                print("Inserting new customer to database...")                 
    
    
    
                sql_string = 'insert into customers(first_name, last_name) values(%s, %s) RETURNING customer_id'     
    
                self.cur.execute(sql_string, (json_payload['first_name'], json_payload['last_name']))
    
                customer_id = self.cur.fetchone()[0]
    
    
    
                print("Customer successfully inserted to database, new customer_id is " + str(customer_id))
    
    
    
                print("Inserting customer's loan record...")    
    
    
    
                sql_string = 'insert into loans(customer_id, amount) values(%s, %s) RETURNING loan_id'     
    
                self.cur.execute(sql_string, (customer_id, json_payload['loan_amount']))
    
                loan_id = self.cur.fetchone()[0]
    
    
    
                print("Customer loan record inserted successfully, new loan_id is " + str(loan_id))
    
    
    
                self.db_conn.commit()
    
    
    
                print("Database transaction completed successfully.")    
    
    
    
                return "Success"
    
    
    
            except (Exception, psycopg2.DatabaseError) as error:
    
    
    
                print("Database transaction failed, rolling back database changes...")
    
    
    
                self.db_conn.rollback()
    
    
    
                return str(error)
    
    
    
            finally:
    
    
    
                if self.db_conn:
    
    
    
                    self.cur.close()
    
                    self.db_conn.close()
    
                    print("Database connection closed successfully.")
  5. 保存并关闭文件。postgresql_db.py

postgresql_db.py文件解释道:

  1. 该语句从 Python 代码加载连接 PostgreSQL 数据库集群的适配器。import psycopg2psycopg2
  2. 该文件包含一个具有两个方法的类。postgresql_db.pyPostgresqlDb
    import psycopg2
    
    
    
    class PostgresqlDb:
    
    
    
        def __init__(self):
    
    
    
            ...
    
    
    
        def execute_db(self, json_payload):
    
    
    
            ...
  3. 该方法是一个构造函数,每次从类创建新对象时都会触发。__init__(...)PostgresqlDb
  4. 该方法从包含客户名称和贷款余额的 HTTP 方法获取 JSON 有效负载,并将请求转发到 PostgreSQL 数据库。execute_db(self, json_payload)POST
  5. 在该方法下,您将 PostgreSQL 参数设置为 。此指令允许您使用命令永久提交成功的事务或命令来阻止部分事务。execute_db(...)autocommitFalsecommit()rollback()
            ...
    
            try: 
    
    
    
                print("Starting new database transaction...")
    
    
    
                self.db_conn.autocommit = False
    
    
    
                self.cur = self.db_conn.cursor()  
    
            ...
  6. 仅当数据库事务中没有错误时,才会触发以下代码块。在事务下,应用程序在表中创建新记录,在表中创建另一条记录。customersloans
                ...
    
    
    
                print("Inserting new customer to database...")                 
    
    
    
                sql_string = 'insert into customers(first_name, last_name) values(%s, %s) RETURNING customer_id'     
    
                self.cur.execute(sql_string, (json_payload['first_name'], json_payload['last_name']))
    
                customer_id = self.cur.fetchone()[0]
    
    
    
                print("Customer successfully inserted to database, new customer_id is " + str(customer_id))
    
    
    
                print("Inserting customer's loan record...")    
    
    
    
                sql_string = 'insert into loans(customer_id, amount) values(%s, %s) RETURNING loan_id'     
    
                self.cur.execute(sql_string, (customer_id, json_payload['loan_amount']))
    
                loan_id = self.cur.fetchone()[0]
    
    
    
                print("Customer loan record inserted successfully, new loan_id is " + str(loan_id))
    
    
    
                self.db_conn.commit()
    
    
    
                print("Database transaction completed successfully.")    
    
    
    
                return "Success"
    
                ...
  7. 当交易失败并出现异常时,该块将触发。然后,块在每种情况下执行以关闭游标和数据库连接。except(...)finally
            ...
    
            except (Exception, psycopg2.DatabaseError) as error:
    
    
    
                print("Database transaction failed, rolling back database changes...")
    
    
    
                self.db_conn.rollback()
    
    
    
                return str(error)
    
    
    
            finally:
    
    
    
                if self.db_conn:
    
    
    
                    self.cur.close()
    
                    self.db_conn.close()
    
                    print("Database connection closed successfully.")

该课程现已准备就绪。使用以下语法将其包含在其他 Python 源代码文件中。PostgresqlDb

import postgresql_db

pg = postgresql_db.PostgresqlDb() 



resp = pg.execute_db(...)

按照下一步为 Python 应用程序创建文件。main.py

五、创建应用程序的入口点

要完成此示例应用程序,您需要一个接受端口 上的传入请求的 HTTP 服务器。Python 有一些内置库,可用于执行任务。按照以下步骤创建 HTTP 服务器:POST8080

  1. 在文本编辑器上打开一个新文件。main.py
     $ nano main.py
  2. 在文件中输入以下信息。main.py
    import http.server
    
    from http import HTTPStatus        
    
    import socketserver
    
    
    
    import json 
    
    import postgresql_db
    
    
    
    class httpHandler(http.server.SimpleHTTPRequestHandler):
    
    
    
                def do_POST(self):
    
    
    
                    content_length = int(self.headers['Content-Length'])
    
                    post_data = self.rfile.read(content_length)
    
                    json_payload = json.loads(post_data)  
    
    
    
                    self.send_response(HTTPStatus.OK)
    
                    self.send_header('Content-type', 'application/json')
    
                    self.end_headers()
    
    
    
                    pg = postgresql_db.PostgresqlDb() 
    
    
    
                    resp = pg.execute_db(json_payload)
    
    
    
                    self.wfile.write(bytes( resp + '\r\n', "utf8")) 
    
    
    
    httpServer = socketserver.TCPServer(('', 8080), httpHandler)
    
    
    
    print("Web server started at port 8080")
    
    
    
    try:
    
    
    
        httpServer.serve_forever()
    
    
    
    except KeyboardInterrupt:
    
    
    
        httpServer.server_close()
    
        print("The HTTP server is stopped.")
  3. 保存并关闭文件。main.py

main.py 文件解释说:

  1. 本节加载示例应用程序所需的所有 Python 库。、 和库加载 HTTP 功能。该模块允许您在加载自定义 PostgreSQL 数据库类时使用 JSON 数据。importhttp.serverHTTPStatussocketserverjsonpostgresql_db
    import http.server
    
    from http import HTTPStatus        
    
    import socketserver
    
    
    
    import json 
    
    
    
    import postgresql_db
    
    ...
  2. 是 HTTP 服务器的处理程序类。此类接受来自 HTTP 客户端的 JSON 有效负载。然后在此类下,and 语句调用自定义类以将数据保存到数据库,并使用该语句返回响应。httpHandlerpg = postgresql_db.PostgresqlDb()pg.execute_db(json_payload)PostgresqlDbself.wfile.write(bytes( resp + '\r\n', "utf8"))
    ...
    
    class httpHandler(http.server.SimpleHTTPRequestHandler):
    
    
    
                def do_POST(self):
    
    
    
                    content_length = int(self.headers['Content-Length'])
    
                    post_data = self.rfile.read(content_length)
    
                    json_payload = json.loads(post_data)  
    
    
    
                    self.send_response(HTTPStatus.OK)
    
                    self.send_header('Content-type', 'application/json')
    
                    self.end_headers()
    
    
    
                    pg = postgresql_db.PostgresqlDb() 
    
    
    
                    resp = pg.execute_db(json_payload)
    
    
    
                    self.wfile.write(bytes( resp + '\r\n', "utf8")) 
    
    ...
  3. 文件末尾的以下声明创建一个 Web 服务器,该服务器侦听 HTTP 请求并将请求调度到类。httpHandler
    ...
    
    httpServer = socketserver.TCPServer(('', 8080), httpHandler)
    
    
    
    print("Web server started at port 8080")
    
    
    
    try:
    
    
    
        httpServer.serve_forever()
    
    
    
    except KeyboardInterrupt:
    
    
    
        httpServer.server_close()
    
        print("The HTTP server is stopped.")

现在,您拥有应用程序所需的所有必要的源代码文件。继续执行下一步以测试应用程序。

六、 测试应用程序

对所有 Python 文件进行编码后,最后一步是安装 Python 包、下载库并测试应用程序。请按照以下步骤完成申请:pippsycopg2

  1. 安装 Python 包。pip
    $ sudo apt install -y python3-pip
  2. 使用该软件包为 PostgreSQL 服务器安装库。pippsycopg2-binary
    $ pip install psycopg2-binary

    输出:

    ...
    
    Installing collected packages: psycopg2-binary
    
    Successfully installed psycopg2-binary-2.9.5
  3. 使用该命令运行应用程序。python3
    $ python3 main.py

    输出:

    Web server started at port 8080
  4. 建立与服务器的另一个连接,并运行以下 Linux 命令,将示例 JSON 有效负载发送到应用程序。SSHcurl
        $  curl -X POST http://localhost:8080/ -H 'Content-Type: application/json' -d '{"first_name": "JOHN", "last_name": "DOE", "loan_amount": "4560"}'

    输出:

    "Success"
  5. 从运行 Web 服务器的第一个终端窗口查看下面的输出。事务成功,没有任何错误。
    Web server started at port 8080
    
    
    
    
    
    Starting new database transaction...
    
    Inserting new customer to database...
    
    Customer successfully inserted to database, new customer_id is 1
    
    Inserting customer's loan record...
    
    Customer loan record inserted successfully, new loan_id is 1
    
    Database transaction completed successfully.
    
    Database connection closed successfully.
  6. 尝试发送以下具有错误贷款金额的无效交易。而不是数值。PP
    $  curl -X POST http://localhost:8080/ -H 'Content-Type: application/json' -d '{"first_name": "JOHN", "last_name": "DOE", "loan_amount": "PP"}'

    输出:

    "invalid input syntax for type numeric: \"PP\"..."
  7. 检查第一个终端窗口的输出。这一次,事务将失败,而不对数据库进行任何更改。尽管应用程序将客户的详细信息插入数据库并获取新的 (),但整个事务将根据以下输出回滚。customer_id2
    ..
    
    Starting new database transaction...
    
    Inserting new customer to database...
    
    Customer successfully inserted to database, new customer_id is 2
    
    Inserting customer's loan record...
    
    Database transaction failed, rolling back database changes...
    
    Database connection closed successfully.
  8. 要验证更改,请登录到 PostgreSQL 数据库集群。
    $ psql -h SAMPLE_POSTGRESQL_DB_HOST_STRING.vultrdb.com -p 16751 -U vultradmin defaultdb

    输出:

    Password for user vultradmin:
  9. 输入您的密码,然后按继续。ENTER输出:
    defaultdb=>
  10. 切换到数据库。bank_db
    defaultdb=> \c bank_db;

    输出:

    You are now connected to database "bank_db" as user "vultradmin".
  11. 查询表。customers
    defaultdb=> SELECT
    
                    customer_id,
    
                    first_name,
    
                    last_name
    
                FROM customers;

    输出:

     customer_id | first_name | last_name
    
    -------------+------------+-----------
    
               1 | JOHN       | DOE
    
    (1 row)
  12. 查询表。loans
    defaultdb=> SELECT
    
                    loan_id,
    
                    customer_id,
    
                    amount
    
                FROM loans;

    输出:

     loan_id | customer_id |  amount
    
    ---------+-------------+-----------
    
           1 |           1 | 4560.0000
    
    (1 row)

上述输出确认应用程序的逻辑按预期工作。如果没有 PostgreSQL 事务逻辑,您现在应该有一个孤立的客户记录,而没有匹配的贷款记录。

七、结论

本指南向您展示如何在 Ubuntu 20.04 服务器上使用 Python 实现 PostgreSQL 数据库事务。使用本指南中的源代码创建将数据库工作单元视为一个整体的应用程序。事务可确保数据库一致性并防止可能出现的孤立记录情况。