Files

460 lines
14 KiB
Markdown
Raw Permalink Normal View History

2026-07-21 00:46:38 +08:00
- **Смена tablespace** с кастомного на дефолтный
- **Удаление OWNER** (замена на `current_user`)
- **Удаление ROLE/TABLESPACE** из дампа
- **Разделение по типам объектов**
---
## Этап 1. Создание дампов
### 1.1. Дамп СХЕМЫ (только таблицы, без функций/триггеров)
```bash
pg_dump -p 5436 -U postgres -d edemo_ehd1 \
--schema-only \
--clean \
--if-exists \
--exclude-function \
--exclude-trigger \
--exclude-table="*" \
--exclude-schema=pg_catalog,information_schema \
2>/dev/null | \
grep -vE '^(CREATE|ALTER|DROP) (ROLE|TABLESPACE)' | \
sed -E 's/OWNER TO [^;]+;/OWNER TO current_user;/g' | \
sed -E 's/ TABLESPACE = [^;]+//g' \
> /opt/pg_dumps/01_schema_tables.sql
```
**Что делает:**
- `--schema-only` — только структура
- `--clean --if-exists` — DROP IF EXISTS перед созданием
- `--exclude-function --exclude-trigger` — исключаем функции и триггеры
- `grep -vE` — удаляем строки с ROLE и TABLESPACE
- `sed` — меняем OWNER на current_user и удаляем TABLESPACE
---
### 1.2. Дамп ДАННЫХ (без схемы, с отключенными триггерами)
```bash
pg_dump -p 5436 -U postgres -d edemo_ehd1 \
--data-only \
--disable-triggers \
--no-owner \
--no-privileges \
-F c \
-f /opt/pg_dumps/02_data.dump
```
**Что делает:**
- `--data-only` — только данные
- `--disable-triggers` — отключаем триггеры при дампе
- `--no-owner` — не сохраняем владельцев
- `-F c` — кастомный формат (для скорости)
---
### 1.3. Дамп ФУНКЦИЙ И ПРОЦЕДУР
```bash
pg_dump -p 5436 -U postgres -d edemo_ehd1 \
--schema-only \
--clean \
--if-exists \
--function \
--procedure \
2>/dev/null | \
grep -vE '^(CREATE|ALTER|DROP) (ROLE|TABLESPACE)' | \
sed -E 's/OWNER TO [^;]+;/OWNER TO current_user;/g' | \
sed -E 's/ TABLESPACE = [^;]+//g' \
> /opt/pg_dumps/03_functions.sql
```
**Что делает:**
- `--function --procedure` — выгружаем только функции и процедуры
- Остальное аналогично пункту 1.1
---
### 1.4. Дамп ТРИГГЕРОВ
```bash
pg_dump -p 5436 -U postgres -d edemo_ehd1 \
--schema-only \
--clean \
--if-exists \
--trigger \
2>/dev/null | \
grep -vE '^(CREATE|ALTER|DROP) (ROLE|TABLESPACE)' | \
sed -E 's/OWNER TO [^;]+;/OWNER TO current_user;/g' | \
sed -E 's/ TABLESPACE = [^;]+//g' \
> /opt/pg_dumps/04_triggers.sql
```
---
### 1.5. Дамп ПРЕДСТАВЛЕНИЙ (VIEWS)
```bash
# Полный дамп схемы
pg_dump -p 5436 -U postgres -d edemo_ehd1 \
--schema-only \
--clean \
--if-exists \
2>/dev/null | \
grep -vE '^(CREATE|ALTER|DROP) (ROLE|TABLESPACE)' | \
sed -E 's/OWNER TO [^;]+;/OWNER TO current_user;/g' | \
sed -E 's/ TABLESPACE = [^;]+//g' \
> /opt/pg_dumps/05_full_schema_clean.sql
# Извлекаем только VIEWS
grep -E 'CREATE (OR REPLACE )?VIEW' /opt/pg_dumps/05_full_schema_clean.sql \
> /opt/pg_dumps/05_views.sql
# Удаляем временный файл
rm -f /opt/pg_dumps/05_full_schema_clean.sql
```
---
### 1.6. (Опционально) Дамп ВНЕШНИХ КЛЮЧЕЙ (если нужно создавать отдельно)
```bash
pg_dump -p 5436 -U postgres -d edemo_ehd1 \
--schema-only \
--clean \
--if-exists \
--constraint \
2>/dev/null | \
grep -vE '^(CREATE|ALTER|DROP) (ROLE|TABLESPACE)' | \
sed -E 's/OWNER TO [^;]+;/OWNER TO current_user;/g' | \
sed -E 's/ TABLESPACE = [^;]+//g' \
> /opt/pg_dumps/06_constraints.sql
```
---
## Этап 2. Восстановление в целевой БД
### 2.0. Подготовка целевой БД
```bash
# Параметры
NEW_HOST="new_host"
USER="postgres"
NEW_DB="new_db"
# Создаем БД (если не существует)
createdb -h $NEW_HOST -U $USER $NEW_DB
# Или пересоздаем
dropdb -h $NEW_HOST -U $USER --if-exists $NEW_DB
createdb -h $NEW_HOST -U $USER $NEW_DB
# Убеждаемся, что используется дефолтный tablespace
psql -h $NEW_HOST -U $USER -d $NEW_DB -c "SHOW default_tablespace;"
```
---
### 2.1. Восстановление ТАБЛИЦ (схема)
```bash
echo "=== ШАГ 2.1: Восстановление таблиц ==="
psql -h $NEW_HOST -U $USER -d $NEW_DB -f /opt/pg_dumps/01_schema_tables.sql
# Проверяем
psql -h $NEW_HOST -U $USER -d $NEW_DB -c "\dt" | head -20
```
**Ожидаемый результат:** Все таблицы созданы, без функций и триггеров.
---
### 2.2. Восстановление ДАННЫХ
```bash
echo "=== ШАГ 2.2: Восстановление данных ==="
pg_restore -h $NEW_HOST -U $USER -d $NEW_DB \
--data-only \
--disable-triggers \
--no-owner \
--no-privileges \
-j 1 \
/opt/pg_dumps/02_data.dump
# Проверяем количество записей
psql -h $NEW_HOST -U $USER -d $NEW_DB -c "
SELECT schemaname, tablename, n_live_tup
FROM pg_stat_user_tables
WHERE schemaname = 'public'
ORDER BY n_live_tup DESC
LIMIT 10;"
```
**Ожидаемый результат:** Данные залиты, триггеры не срабатывали.
---
### 2.3. Восстановление ФУНКЦИЙ И ПРОЦЕДУР
```bash
echo "=== ШАГ 2.3: Восстановление функций и процедур ==="
# Проверяем, есть ли уже функции
psql -h $NEW_HOST -U $USER -d $NEW_DB <<EOF
SELECT proname, pronargs
FROM pg_proc
WHERE pronamespace::regnamespace::text = 'public'
ORDER BY proname;
EOF
# Восстанавливаем
psql -h $NEW_HOST -U $USER -d $NEW_DB -f /opt/pg_dumps/03_functions.sql
# Если ошибка "already exists", добавляем OR REPLACE
if [ $? -ne 0 ]; then
echo "⚠️ Ошибка, пробуем с CREATE OR REPLACE..."
sed -i.bak 's/CREATE FUNCTION/CREATE OR REPLACE FUNCTION/g' /opt/pg_dumps/03_functions.sql
sed -i.bak 's/CREATE PROCEDURE/CREATE OR REPLACE PROCEDURE/g' /opt/pg_dumps/03_functions.sql
psql -h $NEW_HOST -U $USER -d $NEW_DB -f /opt/pg_dumps/03_functions.sql
fi
# Проверяем
psql -h $NEW_HOST -U $USER -d $NEW_DB -c "
SELECT proname, pronargs
FROM pg_proc
WHERE pronamespace::regnamespace::text = 'public'
ORDER BY proname;"
```
**Ожидаемый результат:** Все функции созданы корректно.
---
### 2.4. Восстановление ТРИГГЕРОВ
```bash
echo "=== ШАГ 2.4: Восстановление триггеров ==="
# Удаляем существующие триггеры (если есть)
psql -h $NEW_HOST -U $USER -d $NEW_DB <<EOF
DO \$\$
DECLARE
r RECORD;
BEGIN
FOR r IN
SELECT tgname, tgrelid::regclass::text as table_name
FROM pg_trigger
WHERE tgisinternal = false
AND tgrelid::regclass::text LIKE 'public.%'
LOOP
EXECUTE format('DROP TRIGGER IF EXISTS %I ON %s;', r.tgname, r.table_name);
END LOOP;
END \$\$;
EOF
# Восстанавливаем триггеры
psql -h $NEW_HOST -U $USER -d $NEW_DB -f /opt/pg_dumps/04_triggers.sql
# Проверяем
psql -h $NEW_HOST -U $USER -d $NEW_DB -c "
SELECT
tgname as trigger_name,
tgrelid::regclass::text as table_name,
tgfoid::regproc as function_name
FROM pg_trigger
WHERE tgisinternal = false
AND tgrelid::regclass::text LIKE 'public.%'
ORDER BY tgname;"
```
**Ожидаемый результат:** Триггеры созданы, но пока отключены (или включены, зависит от дампа).
---
### 2.5. Восстановление ПРЕДСТАВЛЕНИЙ (VIEWS)
```bash
echo "=== ШАГ 2.5: Восстановление представлений ==="
# Проверяем, есть ли уже представления
psql -h $NEW_HOST -U $USER -d $NEW_DB -c "\dv"
# Восстанавливаем
psql -h $NEW_HOST -U $USER -d $NEW_DB -f /opt/pg_dumps/05_views.sql
# Проверяем
psql -h $NEW_HOST -U $USER -d $NEW_DB -c "
SELECT schemaname, viewname
FROM pg_views
WHERE schemaname = 'public'
ORDER BY viewname;"
```
**Ожидаемый результат:** Все представления созданы.
---
### 2.6. ВКЛЮЧАЕМ ТРИГГЕРЫ
```bash
echo "=== ШАГ 2.6: Включение триггеров ==="
# Включаем триггеры
psql -h $NEW_HOST -U $USER -d $NEW_DB -c "SET session_replication_role = 'origin';"
# Проверяем статус триггеров
psql -h $NEW_HOST -U $USER -d $NEW_DB -c "
SELECT
tgname as trigger_name,
tgrelid::regclass::text as table_name,
CASE tgenabled
WHEN 'O' THEN '✅ ON (включен)'
WHEN 'D' THEN '❌ DISABLED (отключен)'
WHEN 'R' THEN '🔄 REPLICA'
WHEN 'A' THEN '🔄 ALWAYS'
END as status
FROM pg_trigger
WHERE tgisinternal = false
AND tgrelid::regclass::text LIKE 'public.%'
ORDER BY tgname;"
```
**Ожидаемый результат:** Все триггеры включены (status = ON).
---
## Этап 3. Проверка целостности
### 3.1. Проверка внешних ключей
```bash
psql -h $NEW_HOST -U $USER -d $NEW_DB <<EOF
DO \$\$
DECLARE
r RECORD;
v_count BIGINT;
v_error_count INT := 0;
BEGIN
RAISE NOTICE '=== ПРОВЕРКА ВНЕШНИХ КЛЮЧЕЙ ===';
FOR r IN
SELECT
tc.table_schema,
tc.table_name,
tc.constraint_name,
ccu.table_name as fk_table
FROM information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY'
AND tc.table_schema = 'public'
LOOP
EXECUTE format(
'SELECT COUNT(*) FROM %I.%I WHERE NOT EXISTS (SELECT 1 FROM %I.%I)',
r.table_schema, r.table_name, r.table_schema, r.fk_table
) INTO v_count;
IF v_count > 0 THEN
RAISE NOTICE '⚠️ %: % сиротских записей (ссылаются на %)',
r.constraint_name, v_count, r.fk_table;
v_error_count := v_error_count + 1;
END IF;
END LOOP;
IF v_error_count = 0 THEN
RAISE NOTICE '✅ Все внешние ключи корректны!';
ELSE
RAISE NOTICE '❌ Найдено проблем: %', v_error_count;
END IF;
END \$\$;
EOF
```
---
### 3.2. Проверка работы триггеров
```bash
psql -h $NEW_HOST -U $USER -d $NEW_DB <<EOF
-- Проверяем, что триггеры активны
SELECT
tgname,
tgenabled,
tgfoid::regproc as function_name
FROM pg_trigger
WHERE tgisinternal = false
AND tgenabled IN ('O', 'A') -- ON или ALWAYS
AND tgrelid::regclass::text LIKE 'public.%';
-- Тестовый INSERT для проверки триггера (замените на вашу таблицу)
-- BEGIN;
-- INSERT INTO public."EdsDocumentModelTask" (...) VALUES (...);
-- SELECT * FROM public."BarcodeOldBarcodePair" WHERE ...;
-- ROLLBACK;
EOF
```
---
### 3.3. Итоговая проверка
```bash
psql -h $NEW_HOST -U $USER -d $NEW_DB <<EOF
\echo '=== ОБЩАЯ СТАТИСТИКА ==='
-- Количество таблиц
SELECT 'Таблицы: ' || count(*) as info
FROM pg_tables
WHERE schemaname = 'public';
-- Количество функций
SELECT 'Функции: ' || count(*) as info
FROM pg_proc
WHERE pronamespace::regnamespace::text = 'public';
-- Количество триггеров
SELECT 'Триггеры: ' || count(*) as info
FROM pg_trigger
WHERE tgisinternal = false
AND tgrelid::regclass::text LIKE 'public.%';
-- Количество представлений
SELECT 'Представления: ' || count(*) as info
FROM pg_views
WHERE schemaname = 'public';
-- Размер БД
SELECT pg_size_pretty(pg_database_size(current_database())) as db_size;
EOF
```
---
## Итоговый список файлов
```bash
/opt/pg_dumps/
├── 01_schema_tables.sql # Только таблицы (+ индексы, PK)
├── 02_data.dump # Данные (кастомный формат)
├── 03_functions.sql # Функции и процедуры
├── 04_triggers.sql # Триггеры
├── 05_views.sql # Представления
└── 06_constraints.sql # (опционально) Внешние ключи
```
---
## Важные моменты
1. **Все дампы очищены** от `ROLE`, `TABLESPACE` и `OWNER`
2. **Tablespace заменен** на дефолтный
3. **OWNER заменен** на `current_user`
4. **Порядок восстановления** строго соблюден
5. **Триггеры отключены** при заливке данных
6. **Функции создаются** после таблиц и данных
7. **Триггеры создаются** после функций
8. **Представления создаются** после таблиц
Этот подход гарантирует успешную миграцию без ошибок! 🚀