不止于连接:用Python+Psycopg2玩转openGauss,从增删改查到事务实战

在数据库应用开发中,仅仅建立连接只是万里长征的第一步。本文将带你深入探索如何利用Python的Psycopg2库与openGauss数据库进行高效交互,从基础的增删改查操作到复杂的事务处理,构建一个完整的员工信息管理系统实战项目。

1. 环境准备与高级连接配置

在开始实战之前,确保已完成以下准备工作:

  • Python 3.6+环境
  • openGauss数据库服务正常运行
  • Psycopg2库已安装(推荐版本2.8.6+)

安全连接配置示例

import psycopg2
from psycopg2 import pool

# 创建连接池
connection_pool = psycopg2.pool.ThreadedConnectionPool(
    minconn=1,
    maxconn=10,
    database="hr_system",
    user="app_user",
    password="SecurePass123!",
    host="db.example.com",
    port="5432",
    sslmode="require",
    sslrootcert="/path/to/root.crt"
)

提示:生产环境务必使用SSL加密连接,避免敏感数据在传输过程中被窃取

连接参数对比表:

参数 openGauss PostgreSQL 说明
sslmode 支持 支持 安全连接模式
sslrootcert 必需 可选 根证书路径
application_name 支持 支持 应用标识
connect_timeout 支持 支持 连接超时设置

2. 构建员工信息管理系统核心功能

2.1 数据库模型设计

首先设计员工管理系统的基础表结构:

-- 部门表
CREATE TABLE departments (
    dept_id SERIAL PRIMARY KEY,
    dept_name VARCHAR(50) NOT NULL,
    location VARCHAR(100)
);

-- 员工表
CREATE TABLE employees (
    emp_id SERIAL PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    email VARCHAR(100) UNIQUE,
    hire_date DATE NOT NULL,
    salary NUMERIC(10,2) CHECK (salary > 0),
    dept_id INTEGER REFERENCES departments(dept_id)
);

-- 薪资调整记录表
CREATE TABLE salary_history (
    history_id SERIAL PRIMARY KEY,
    emp_id INTEGER REFERENCES employees(emp_id),
    old_salary NUMERIC(10,2),
    new_salary NUMERIC(10,2),
    change_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    reason VARCHAR(200)
);

2.2 参数化查询与防注入实践

使用参数化查询是防止SQL注入的关键:

def add_employee(first_name, last_name, email, hire_date, salary, dept_id):
    conn = connection_pool.getconn()
    try:
        with conn.cursor() as cur:
            # 使用参数化查询
            cur.execute(
                "INSERT INTO employees (first_name, last_name, email, hire_date, salary, dept_id) "
                "VALUES (%s, %s, %s, %s, %s, %s) RETURNING emp_id",
                (first_name, last_name, email, hire_date, salary, dept_id)
            )
            new_id = cur.fetchone()[0]
            conn.commit()
            return new_id
    except psycopg2.Error as e:
        conn.rollback()
        print(f"Error adding employee: {e}")
        return None
    finally:
        connection_pool.putconn(conn)

2.3 批量数据处理技巧

处理大量数据时,使用executemany和服务器端游标提高效率:

def bulk_insert_employees(employee_list):
    conn = connection_pool.getconn()
    try:
        with conn.cursor() as cur:
            # 准备数据
            data = [(emp['first_name'], emp['last_name'], emp['email'], 
                     emp['hire_date'], emp['salary'], emp['dept_id']) 
                    for emp in employee_list]
            
            # 批量插入
            cur.executemany(
                "INSERT INTO employees (first_name, last_name, email, hire_date, salary, dept_id) "
                "VALUES (%s, %s, %s, %s, %s, %s)",
                data
            )
            conn.commit()
            return True
    except psycopg2.Error as e:
        conn.rollback()
        print(f"Bulk insert failed: {e}")
        return False
    finally:
        connection_pool.putconn(conn)

3. 事务管理与数据一致性

3.1 基本事务控制

openGauss的事务特性与PostgreSQL高度兼容:

def transfer_employee(emp_id, from_dept, to_dept):
    conn = connection_pool.getconn()
    try:
        with conn.cursor() as cur:
            # 开始事务
            conn.autocommit = False
            
            # 检查员工当前部门
            cur.execute("SELECT dept_id FROM employees WHERE emp_id = %s FOR UPDATE", (emp_id,))
            current_dept = cur.fetchone()
            
            if not current_dept or current_dept[0] != from_dept:
                raise ValueError("Employee not in source department")
            
            # 更新部门
            cur.execute(
                "UPDATE employees SET dept_id = %s WHERE emp_id = %s",
                (to_dept, emp_id)
            )
            
            # 记录调动
            cur.execute(
                "INSERT INTO employee_transfers (emp_id, from_dept, to_dept, transfer_date) "
                "VALUES (%s, %s, %s, CURRENT_TIMESTAMP)",
                (emp_id, from_dept, to_dept)
            )
            
            conn.commit()
            return True
    except Exception as e:
        conn.rollback()
        print(f"Transfer failed: {e}")
        return False
    finally:
        conn.autocommit = True
        connection_pool.putconn(conn)

3.2 保存点(Savepoint)应用

复杂事务中使用保存点实现部分回滚:

def complex_employee_update(emp_id, updates):
    conn = connection_pool.getconn()
    try:
        with conn.cursor() as cur:
            conn.autocommit = False
            
            # 创建保存点
            cur.execute("SAVEPOINT start_update")
            
            # 更新基本信息
            if 'email' in updates:
                cur.execute(
                    "UPDATE employees SET email = %s WHERE emp_id = %s",
                    (updates['email'], emp_id)
                )
            
            # 薪资调整需要特殊处理
            if 'salary' in updates:
                # 获取当前薪资
                cur.execute("SELECT salary FROM employees WHERE emp_id = %s", (emp_id,))
                old_salary = cur.fetchone()[0]
                
                # 验证新薪资
                if updates['salary'] < old_salary * 0.9:
                    # 薪资降幅超过10%需要额外审批
                    raise ValueError("Salary decrease exceeds 10% limit")
                
                # 更新薪资
                cur.execute(
                    "UPDATE employees SET salary = %s WHERE emp_id = %s",
                    (updates['salary'], emp_id)
                )
                
                # 记录薪资变更
                cur.execute(
                    "INSERT INTO salary_history (emp_id, old_salary, new_salary, reason) "
                    "VALUES (%s, %s, %s, %s)",
                    (emp_id, old_salary, updates['salary'], updates.get('reason', 'Regular adjustment'))
                )
            
            conn.commit()
            return True
            
    except ValueError as ve:
        # 回滚到保存点
        cur.execute("ROLLBACK TO SAVEPOINT start_update")
        conn.commit()
        print(f"Validation error: {ve}")
        return False
    except Exception as e:
        conn.rollback()
        print(f"Update failed: {e}")
        return False
    finally:
        conn.autocommit = True
        connection_pool.putconn(conn)

4. 高级特性与性能优化

4.1 游标分页查询

处理大量数据时使用游标分页:

def get_employees_by_department(dept_id, page_size=100):
    conn = connection_pool.getconn()
    try:
        with conn.cursor(name='employee_cursor') as cur:
            # 使用命名游标
            cur.itersize = page_size  # 每次从服务器获取的行数
            cur.execute(
                "SELECT emp_id, first_name, last_name, email, salary "
                "FROM employees WHERE dept_id = %s ORDER BY emp_id",
                (dept_id,)
            )
            
            while True:
                employees = cur.fetchmany(page_size)
                if not employees:
                    break
                
                for emp in employees:
                    yield {
                        'id': emp[0],
                        'name': f"{emp[1]} {emp[2]}",
                        'email': emp[3],
                        'salary': emp[4]
                    }
    finally:
        connection_pool.putconn(conn)

4.2 连接池监控与管理

合理管理连接池资源:

def monitor_connection_pool():
    stats = {
        'total_connections': connection_pool.maxconn,
        'idle_connections': connection_pool.maxconn - len(connection_pool._used),
        'waiting_requests': connection_pool._waiting,
        'connection_timeout': connection_pool._timeout
    }
    
    # 记录连接池使用情况
    with open('connection_pool.log', 'a') as f:
        f.write(f"{datetime.now()}: {stats}\n")
    
    return stats

# 定期执行监控
import threading

def start_pool_monitor(interval=300):
    def monitor():
        while True:
            monitor_connection_pool()
            time.sleep(interval)
    
    thread = threading.Thread(target=monitor, daemon=True)
    thread.start()

4.3 openGauss特有功能利用

利用openGauss的MOT内存表提升性能:

def create_mot_table():
    conn = connection_pool.getconn()
    try:
        with conn.cursor() as cur:
            # 创建内存优化表
            cur.execute("""
                CREATE FOREIGN TABLE employee_cache (
                    emp_id INTEGER PRIMARY KEY,
                    name VARCHAR(100),
                    dept_name VARCHAR(50),
                    salary NUMERIC(10,2)
                ) SERVER mot_server
            """)
            
            # 初始化缓存数据
            cur.execute("""
                INSERT INTO employee_cache
                SELECT e.emp_id, e.first_name || ' ' || e.last_name, d.dept_name, e.salary
                FROM employees e JOIN departments d ON e.dept_id = d.dept_id
            """)
            
            conn.commit()
            return True
    except psycopg2.Error as e:
        conn.rollback()
        print(f"Failed to create MOT table: {e}")
        return False
    finally:
        connection_pool.putconn(conn)

在实际项目中,我发现合理使用连接池和事务隔离级别能显著提升应用性能。特别是在高并发场景下,设置合适的连接池大小和事务超时时间至关重要。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐