Hôm nay tôi muốn giới thiệu với người đọc Habr một tiện ích được viết bằng Python để làm việc với các phụ thuộc bảng trong Cơ sở dữ liệu PostgreSQL.
API của tiện ích này rất đơn giản và bao gồm ba phương thức:
- archive_table - lưu trữ/xóa đệ quy các hàng có Khóa chính được chỉ định
- get_table_references - tìm kiếm các phụ thuộc cho một bảng (sẽ hiển thị các bảng được tham chiếu bởi bảng được chỉ định và các bảng tham chiếu đến nó)
- get_rows_references - tìm kiếm các hàng trong các bảng khác tham chiếu các hàng được chỉ định trong bảng mong muốn
thời tiền sử
Tên tôi là Oleg Borzov, tôi là nhà phát triển trong nhóm CRM dành cho người quản lý cho vay thế chấp ở Domklik.
Cơ sở dữ liệu chính của hệ thống CRM của chúng tôi là một trong những cơ sở dữ liệu lớn nhất về số lượng trong công ty. Nó cũng là một trong những cái lâu đời nhất: nó xuất hiện ngay khi bắt đầu dự án, khi cây còn lớn, Domklik là một công ty khởi nghiệp và thay vì một dịch vụ vi mô trên khung không đồng bộ Python thời thượng, có một khối nguyên khối khổng lồ trong PHP.
Quá trình chuyển đổi từ PHP sang Python diễn ra rất lâu và cần có sự hỗ trợ đồng thời của cả hai hệ thống, điều này ảnh hưởng đến thiết kế cơ sở dữ liệu.
Kết quả là, chúng tôi có một cơ sở dữ liệu với một số lượng lớn các bảng khổng lồ và có tính kết nối cao cùng với một loạt chỉ mục cho các loại truy vấn khác nhau. Tất cả điều này ảnh hưởng tiêu cực đến hiệu suất của cơ sở dữ liệu: do các bảng lớn và nhiều mối quan hệ giữa chúng, độ phức tạp của các truy vấn không ngừng tăng lên, điều này đặc biệt quan trọng đối với các bảng được tải nhiều nhất.
Để giảm tải cho cơ sở dữ liệu, chúng tôi quyết định viết một tập lệnh chuyển các bản ghi cũ từ các bảng có dung lượng lớn và được tải nhiều nhất sang các bảng được lưu trữ (ví dụ: từ task
в task_archive
).
Nhiệm vụ này phức tạp bởi số lượng lớn các mối quan hệ giữa các bảng: chỉ cần di chuyển các hàng từ task
в task_archive
vẫn chưa đủ, trước đó bạn cần thực hiện đệ quy tương tự với tất cả những tham chiếu đó task
những cái bàn.
Tôi sẽ chứng minh bằng một ví dụ
Giả sử chúng ta cần xóa các bản ghi khỏi một bảng Flights
. Postgres sẽ không cho phép chúng ta làm điều này giống như vậy: trước tiên chúng ta cần xóa các bản ghi khỏi tất cả các bảng tham chiếu, v.v. theo cách đệ quy xuống các bảng không được bất kỳ ai tham chiếu.
Trong ví dụ của chúng tôi tại Flights
đề cập đến Ticket_flights
, và trên người cô ấy - Boarding_passes
.
Vì vậy, bạn cần xóa nó theo thứ tự sau:
- Chúng tôi nhận được các giá trị khóa chính (PK) của các hàng trong
Ticket_flights
, đề cập đến các hàng sẽ bị xóa trongFlights
. - Chúng tôi nhận được hàng PK
Boarding_passes
, đề cập đếnTicket_flights
. - Chúng ta xóa các hàng bằng PK ở bước 2 trong bảng
Boarding_passes
. - Xóa dòng bằng PK ở bước 1
Ticket_flights
. - Xóa các dòng khỏi
Flights
.
Kết quả là chúng tôi đã tạo ra một tiện ích có tên PGGraph, tiện ích này đã quyết định tạo thành nguồn mở.
Cách sử dụng
Tiện ích hỗ trợ hai chế độ sử dụng:
- Gọi từ dòng lệnh (
pggraph …
). - Cách sử dụng trong mã Python (lớp
PgGraphApi
).
Cài đặt và cấu hình
Đầu tiên bạn cần cài đặt tiện ích từ kho Pypi:
pip3 install pggraph
Sau đó tạo tệp config.ini trên máy cục bộ với cấu hình cơ sở dữ liệu và tập lệnh lưu trữ:
[db]
host = localhost
port = 5432
user = postgres
password = postgres
dbname = postgres
schema = public ; Необязательный параметр, указано значение по умолчанию
[archive] ; Данный раздел заполнять необязательно, ниже указаны значения по умолчанию
is_debug = false
chunk_size = 1000
max_depth = 20
to_archive = true
archive_suffix = 'archive'
Chạy từ bảng điều khiển
Tham số
$ pggraph -h
usage: pggraph action [-h] --table TABLE [--ids IDS] [--config_path CONFIG_PATH]
positional arguments:
action required action: archive_table, get_table_references, get_rows_references
optional arguments:
-h, --help show this help message and exit
--table TABLE table name
--ids IDS primary key ids, separated by comma, e.g. 1,2,3
--config_path CONFIG_PATH path to config.ini
--log_path LOG_PATH path to log dir
--log_level LOG_LEVEL log level (debug, info, error)
Đối số vị trí:
action
- Yêu cầu hành động:archive_table
,get_table_references
hoặcget_rows_references
.
Đối số được đặt tên:
--config_path
- đường dẫn đến tập tin cấu hình;--table
— một bảng mà bạn cần thực hiện một hành động;--ids
- danh sách id được phân tách bằng dấu phẩy, ví dụ:1,2,3
(tham số tùy chọn);--log_path
— đường dẫn đến thư mục chứa nhật ký (tham số tùy chọn, theo mặc định - thư mục chính);--log_level
— mức ghi nhật ký (tham số tùy chọn, mặc định là INFO).
Ví dụ lệnh
Lưu trữ một bảng
Chức năng chính của tiện ích là lưu trữ dữ liệu, tức là. chuyển các hàng từ bảng chính sang bảng lưu trữ (ví dụ: từ bảng sách в sách_archive).
Xóa mà không lưu trữ cũng được hỗ trợ: để làm được điều này, bạn cần đặt tham số trong config.ini to_archive = sai).
Các thông số bắt buộc - config_path, bảng và id.
Sau khi khởi chạy, các bản ghi sẽ bị xóa đệ quy ids
trong bảng table
và trong tất cả các bảng tham chiếu đến nó.
$ pggraph archive_table --config_path config.hw.local.ini --table flights --ids 1,2,3
2020-06-20 19:27:44 INFO: flights - START
2020-06-20 19:27:44 INFO: flights - start archive_recursive 3 rows (depth=0)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: ticket_flights - start archive_recursive 3 rows (depth=1)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO: boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO: boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO: boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: ticket_flights - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: flights - archive_by_ids 3 rows by id
2020-06-20 19:27:44 INFO: flights - END
Tìm phần phụ thuộc cho một bảng được chỉ định
Hàm tìm phụ thuộc của một bảng được chỉ định table
. Các thông số bắt buộc - config_path
и table
.
Sau khi khởi chạy, một từ điển sẽ được hiển thị trên màn hình, trong đó:
in_refs
- một từ điển các bảng tham chiếu đến một bảng nhất định, trong đó khóa là tên của bảng, giá trị là danh sách các đối tượng Khóa ngoài (pk_main
- khóa chính trong bảng chính,pk_ref
- khóa chính trong bảng tham chiếu,fk_ref
— tên của cột là khóa ngoại của bảng nguồn);out_refs
- một từ điển các bảng mà cái này đề cập đến.
$ pggraph get_table_references --config_path config.hw.local.ini --table flights
{'in_refs': {'ticket_flights': [ForeignKey(pk_main='flight_id', pk_ref='ticket_no, flight_id', fk_ref='flight_id')]},
'out_refs': {'aircrafts': [ForeignKey(pk_main='aircraft_code', pk_ref='flight_id', fk_ref='aircraft_code')],
'airports': [ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='arrival_airport'),
ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='departure_airport')]}}
Tìm tham chiếu đến chuỗi có Khóa chính được chỉ định
Chức năng tìm kiếm các hàng trong bảng khác tham chiếu đến các hàng thông qua Khóa ngoài ids
bảng table
. Các thông số bắt buộc - config_path
, table
и ids
.
Sau khi khởi chạy, trên màn hình sẽ hiển thị một từ điển có cấu trúc như sau:
{
pk_id_1: {
reffering_table_name_1: {
foreign_key_1: [
{row_pk_1: value, row_pk_2: value},
...
],
...
},
...
},
pk_id_2: {...},
...
}
Cuộc gọi ví dụ:
$ pggraph get_rows_references --config_path config.hw.local.ini --table flights --ids 1,2,3
{1: {'ticket_flights': {'flight_id': [{'flight_id': 1,
'ticket_no': '0005432816945'},
{'flight_id': 1,
'ticket_no': '0005432816941'}]}},
2: {'ticket_flights': {'flight_id': [{'flight_id': 2,
'ticket_no': '0005433101832'},
{'flight_id': 2,
'ticket_no': '0005433101864'},
{'flight_id': 2,
'ticket_no': '0005432919715'}]}},
3: {'ticket_flights': {'flight_id': [{'flight_id': 3,
'ticket_no': '0005432817560'},
{'flight_id': 3,
'ticket_no': '0005432817568'},
{'flight_id': 3,
'ticket_no': '0005432817559'}]}}}
Cách sử dụng trong mã
Ngoài việc chạy nó trong bảng điều khiển, thư viện có thể được sử dụng bằng mã Python. Ví dụ về các cuộc gọi trong môi trường tương tác iPython được hiển thị bên dưới.
Lưu trữ một bảng
>>> from pg_graph.main import setup_logging
>>> setup_logging(log_level='DEBUG')
>>> from pg_graph.api import PgGraphApi
>>> api = PgGraphApi('config.hw.local.ini')
>>> api.archive_table('flights', [4,5])
2020-06-20 23:12:08 INFO: flights - START
2020-06-20 23:12:08 INFO: flights - start archive_recursive 2 rows (depth=0)
2020-06-20 23:12:08 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: ticket_flights - ForeignKey(pk_main='flight_id', pk_ref='flight_id, ticket_no', fk_ref='flight_id')
2020-06-20 23:12:08 DEBUG: SQL('SELECT flight_id, ticket_no FROM bookings.ticket_flights WHERE (flight_id) IN (%s, %s)')
2020-06-20 23:12:08 INFO: ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: ticket_flights - start archive_recursive 3 rows (depth=1)
2020-06-20 23:12:08 INFO: START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: boarding_passes - archive_by_fk 3 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: DELETE FROM boarding_passes by FK flight_id, ticket_no - 3 rows
2020-06-20 23:12:08 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: ticket_flights - archive_by_ids 3 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: DELETE FROM ticket_flights by flight_id, ticket_no - 3 rows
2020-06-20 23:12:08 DEBUG: INSERT INTO ticket_flights_archive - 3 rows
2020-06-20 23:12:08 INFO: END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: flights - archive_by_ids 2 rows by flight_id
2020-06-20 23:12:09 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.flights_archive (LIKE bookings.flights)')
2020-06-20 23:12:09 DEBUG: DELETE FROM flights by flight_id - 2 rows
2020-06-20 23:12:09 DEBUG: INSERT INTO flights_archive - 2 rows
2020-06-20 23:12:09 INFO: flights - END
Tìm phần phụ thuộc cho một bảng được chỉ định
>>> from pg_graph.api import PgGraphApi
>>> from pprint import pprint
>>> api = PgGraphApi('config.hw.local.ini')
>>> res = api.get_table_references('flights')
>>> pprint(res)
{'in_refs': {'ticket_flights': [ForeignKey(pk_main='flight_id', pk_ref='flight_id, ticket_no', fk_ref='flight_id')]},
'out_refs': {'aircrafts': [ForeignKey(pk_main='aircraft_code', pk_ref='flight_id', fk_ref='aircraft_code')],
'airports': [ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='arrival_airport'),
ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='departure_airport')]}}
Tìm tham chiếu đến chuỗi có Khóa chính được chỉ định
>>> from pg_graph.api import PgGraphApi
>>> from pprint import pprint
>>> api = PgGraphApi('config.hw.local.ini')
>>> rows = api.get_rows_references('flights', [1,2,3])
>>> pprint(rows)
{1: {'ticket_flights': {'flight_id': [{'flight_id': 1,
'ticket_no': '0005432816945'},
{'flight_id': 1,
'ticket_no': '0005432816941'}]}},
2: {'ticket_flights': {'flight_id': [{'flight_id': 2,
'ticket_no': '0005433101832'},
{'flight_id': 2,
'ticket_no': '0005433101864'},
{'flight_id': 2,
'ticket_no': '0005432919715'}]}},
3: {'ticket_flights': {'flight_id': [{'flight_id': 3,
'ticket_no': '0005432817560'},
{'flight_id': 3,
'ticket_no': '0005432817568'},
{'flight_id': 3,
'ticket_no': '0005432817559'}]}}}
Mã nguồn thư viện có sẵn tại
Tôi sẽ vui mừng nhận xét, cam kết và đề xuất.
Tôi sẽ cố gắng trả lời các câu hỏi trong khả năng tốt nhất của mình ở đây và trong kho lưu trữ.
Nguồn: www.habr.com