460 lines
14 KiB
Markdown
460 lines
14 KiB
Markdown
- **Смена 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. **Представления создаются** после таблиц
|
||
|
||
Этот подход гарантирует успешную миграцию без ошибок! 🚀 |