Qué vas a conseguir
Que la base de datos del ERP se pueda devolver a cualquier minuto de los últimos días, y que lo hayas comprobado antes de necesitarlo. En una empresa industrial con plantas en varios países el ERP no descansa: cuando en una sede es de noche, en otra se están cerrando pedidos, lanzando órdenes de fabricación o haciendo el cierre de mes. No hay ventana tranquila para copias largas ni margen para descubrir el día del problema que falta un log.
Antes de empezar
- El nombre de la base de datos del ERP y de sus bases auxiliares (en los ejemplos,
ERP_PRODensrv-sql-erp01). - Un servidor de pruebas (
srv-sql-test01) con espacio para restaurar la base entera; nunca se restaura encima de producción para investigar. - El RPO y el RTO acordados con negocio: cuánto dato se puede perder y cuánto puede tardar la vuelta.
- El contacto del consultor o partner del ERP: cualquier corrección de datos directa en tablas del ERP se hace con su visto bueno.
- La zona horaria del servidor SQL y la de cada planta. Las horas de
msdby deSTOPATson las del servidor.
1. El modelo de recuperación
| Modelo | Qué permite | Cuándo |
|---|---|---|
SIMPLE | Solo volver a la última completa o diferencial | Bases de pruebas, staging, informes que se regeneran |
FULL | Copias de log y restauración a un punto en el tiempo | El ERP de producción, siempre |
BULK_LOGGED | Como FULL, pero las cargas masivas van mínimamente registradas | Solo durante una carga puntual; no permite STOPAT dentro de ese log |
SELECT name, recovery_model_desc, log_reuse_wait_desc FROM sys.databases WHERE name LIKE 'ERP%';name recovery_model_desc log_reuse_wait_desc------------ ------------------- -------------------ERP_PROD FULL LOG_BACKUPERP_PROD_DOC SIMPLE NOTHINGALTER DATABASE [ERP_PROD_DOC] SET RECOVERY FULL;LOG_BACKUP es normal en una base FULL: el log espera a la siguiente copia de log. Si una base pasa a FULL, la cadena empieza con la siguiente copia completa; hasta entonces, las copias de log no sirven.
2. La pauta de copias y dónde se guardan
- Completa semanal, la noche del sábado de la sede del servidor, revisando antes que no coincide con el cierre de mes de ninguna planta.
- Diferencial diaria, para no tener que aplicar una semana de logs.
- Log cada 15 minutos (o menos si el RPO lo pide), 24x7.
- Siempre con
CHECKSUMyCOMPRESSION, y a un destino fuera del propio servidor: un recurso compartido de copias o una URL de almacenamiento de objetos.
# Completa, a un recurso compartido (la cuenta del servicio SQL necesita escritura)BACKUP DATABASE [ERP_PROD] TO DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_FULL_20261024.bak' WITH CHECKSUM, COMPRESSION, STATS = 10;# Diferencial diariaBACKUP DATABASE [ERP_PROD] TO DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_DIFF_20261026.bak' WITH DIFFERENTIAL, CHECKSUM, COMPRESSION;# Log cada 15 minutosBACKUP LOG [ERP_PROD] TO DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_LOG_20261027_1045.trn' WITH CHECKSUM, COMPRESSION;# Alternativa: a una URL con una credencial SAS creada para ese contenedorBACKUP DATABASE [ERP_PROD] TO URL = N'https://contosobkp.blob.core.windows.net/sql/ERP_PROD_FULL_20261024.bak' WITH CHECKSUM, COMPRESSION;# Comprobar que el fichero se puede leer y que los checksums cuadranRESTORE VERIFYONLY FROM DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_FULL_20261024.bak' WITH CHECKSUM;The backup set on file 1 is valid.- Un fichero por copia con fecha y hora en el nombre. Escribir siempre sobre el mismo
.bakconINITdeja una única copia y ningún historial. - Si otra herramienta (el agente de la plataforma de copias) hace también copias del ERP, una de las dos debe usar
COPY_ONLY; dos cadenas de diferenciales sobre la misma base terminan en una restauración que no encuentra su completa. VERIFYONLYcomprueba que la copia es legible, no que la base esté sana. Eso solo lo dice una restauración real y unDBCC CHECKDBsobre ella.
3. Retención
| Tipo | En el recurso compartido | Copia fuera de la sede |
|---|---|---|
| Completa | 5 semanas | Una por mes, 12 meses (o lo que pida auditoría) |
| Diferencial | 14 días | No |
| Log | 14 días | Mientras exista la completa de la que dependen |
La limpieza la hace un trabajo del SQL Server Agent o la plataforma de copias, nunca alguien borrando ficheros a mano: un log borrado rompe la cadena desde ese punto. Las copias de cierre de ejercicio se etiquetan y se guardan aparte, porque finanzas o auditoría las pedirán años después.
4. La cadena de restauración en el servidor de pruebas
Para volver a las 10:39 del martes, la cadena es: la última completa anterior, la última diferencial posterior a esa completa y anterior a las 10:39, y todos los logs desde esa diferencial hasta el que contiene las 10:39.
RESTORE DATABASE [ERP_PROD_1039] FROM DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_FULL_20261024.bak' WITH MOVE N'ERP_PROD' TO N'E:\SQLData\ERP_PROD_1039.mdf', MOVE N'ERP_PROD_log' TO N'F:\SQLLog\ERP_PROD_1039.ldf', NORECOVERY, STATS = 10;RESTORE DATABASE [ERP_PROD_1039] FROM DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_DIFF_20261026.bak' WITH NORECOVERY;RESTORE LOG [ERP_PROD_1039] FROM DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_LOG_20261027_0015.trn' WITH NORECOVERY;# ... un RESTORE LOG por cada fichero, en orden ...RESTORE LOG [ERP_PROD_1039] FROM DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_LOG_20261027_1045.trn' WITH STOPAT = N'2026-10-27T10:39:00', RECOVERY;RESTORE LOG successfully processed 1874 pages in 0.412 seconds (35.532 MB/sec).- Los nombres lógicos para
MOVEsalen deRESTORE FILELISTONLY FROM DISK = .... - Mientras la base está en
NORECOVERYse pueden seguir aplicando copias;RECOVERYla abre y cierra la cadena. Si no sabes el minuto exacto, restaura los logs conSTANDBY = N'F:\SQLLog\ERP_PROD_1039.tuf'en lugar deNORECOVERY: la base queda en solo lectura entre log y log y puedes consultarla para ir acercándote. - Anota cuánto ha tardado, con
DBCC CHECKDBal final: esa es tu cifra real de RTO para el ERP. Repite la prueba cada trimestre.
5. El historial de msdb
SELECT TOP (8) bs.type, bs.backup_start_date, bs.backup_finish_date, bs.has_backup_checksums, bs.is_copy_only, bmf.physical_device_name FROM msdb.dbo.backupset bs JOIN msdb.dbo.backupmediafamily bmf ON bs.media_set_id = bmf.media_set_id WHERE bs.database_name = N'ERP_PROD' ORDER BY bs.backup_finish_date DESC;type backup_start_date backup_finish_date has_backup_checksums is_copy_only physical_device_name---- ------------------- ------------------- -------------------- ------------ ----------------------------------------------L 2026-10-27 10:45:00 2026-10-27 10:45:04 1 0 \\srv-bkp01\sql$\ERP_PROD\ERP_PROD_LOG_20261027_1045.trnI 2026-10-26 23:00:00 2026-10-26 23:06:41 1 0 \\srv-bkp01\sql$\ERP_PROD\ERP_PROD_DIFF_20261026.bakD 2026-10-24 22:00:00 2026-10-24 23:12:18 1 0 \\srv-bkp01\sql$\ERP_PROD\ERP_PROD_FULL_20261024.bakD es completa, I diferencial y L log. Con esta consulta montas la lista de ficheros de la cadena sin buscar a ojo en el recurso compartido. msdb.dbo.restorehistory, en el servidor de pruebas, deja constancia de cada restauración que haces para la prueba trimestral.
6. El ticket: "alguien ha borrado filas en el ERP a las 10:40, ¿se pueden recuperar?"
Martes. Desde una planta de otro país avisan de que han desaparecido líneas de pedidos de compra de un proveedor. El ERP sigue en uso en todas las sedes.
- Qué hora es "las 10:40": la de la planta, no la del servidor. Si la planta va una hora por detrás, para el servidor son las 11:40. Confírmalo con el usuario y con el log de auditoría del ERP si lo tiene.
- No se restaura producción: volver todo el ERP atrás borraría el trabajo del resto de plantas desde entonces. Se restaura una copia aparte y se recuperan solo esas filas.
- Asegura el log: lanza una copia de log ya (sin
NORECOVERY, producción sigue abierta) para que el periodo del borrado esté en una copia y no dependa de la siguiente programada. - Restaura en
srv-sql-test01la cadena hasta un minuto antes del borrado (sección 4), con otro nombre de base. - Compara: las filas que están en la copia y no en producción, por la clave de la tabla, en una consulta con el consultor del ERP delante.
- Devuélvelas como diga el ERP: con su herramienta de importación, con un script revisado por el partner (cuidado con columnas
IDENTITY, triggers y tablas relacionadas) o volviéndolas a introducir si son pocas. Lo que no se hace es unINSERTdirecto sin que nadie del ERP lo valide. - Cierra con causa: quién borró, desde qué usuario y por qué pudo hacerlo. Muchas veces es un permiso de más en un perfil del ERP, no un fallo técnico.
Siguientes pasos
- La guía de PostgreSQL/MySQL/SQL Server del Nivel 3 cubre la administración diaria de las bases de datos.
- La de Veeam Backup explica cómo encaja la copia del servidor SQL con la de las máquinas virtuales.
- La de Backup y DR/BCP del Nivel 4 sitúa el ERP dentro del plan de continuidad.
¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.
What you'll get
An ERP database you can take back to any minute of the last few days, and proof that you have done it before you need it. In a manufacturer with plants in several countries the ERP never rests: when it is night at one site, another is closing orders, releasing work orders or running the month-end close. There is no quiet window for long backups and no room to discover, on the day of the problem, that a log is missing.
Before you start
- The name of the ERP database and its auxiliary databases (in the examples,
ERP_PRODonsrv-sql-erp01). - A test server (
srv-sql-test01) with room to restore the whole database; you never restore over production to investigate. - The RPO and RTO agreed with the business: how much data can be lost and how long recovery may take.
- The contact for the ERP consultant or partner: any direct data fix in ERP tables is done with their approval.
- The time zone of the SQL server and of each plant. Times in
msdband inSTOPATare the server's.
1. The recovery model
| Model | What it allows | When |
|---|---|---|
SIMPLE | Only going back to the last full or differential | Test databases, staging, reports that are rebuilt |
FULL | Log backups and point-in-time restore | The production ERP, always |
BULK_LOGGED | Like FULL, but bulk loads are minimally logged | Only during a one-off load; no STOPAT inside that log |
SELECT name, recovery_model_desc, log_reuse_wait_desc FROM sys.databases WHERE name LIKE 'ERP%';name recovery_model_desc log_reuse_wait_desc------------ ------------------- -------------------ERP_PROD FULL LOG_BACKUPERP_PROD_DOC SIMPLE NOTHINGALTER DATABASE [ERP_PROD_DOC] SET RECOVERY FULL;LOG_BACKUP is normal on a FULL database: the log is waiting for the next log backup. When a database moves to FULL, the chain starts with the next full backup; until then, log backups are no use.
2. The backup schedule and where backups go
- Full weekly, on Saturday night at the server's site, checking first that it doesn't clash with any plant's month-end close.
- Differential daily, so you don't have to apply a week of logs.
- Log every 15 minutes (or less if the RPO demands it), 24x7.
- Always with
CHECKSUMandCOMPRESSION, and to a target outside the server itself: a backup share or an object storage URL.
# Full, to a share (the SQL service account needs write access)BACKUP DATABASE [ERP_PROD] TO DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_FULL_20261024.bak' WITH CHECKSUM, COMPRESSION, STATS = 10;# Daily differentialBACKUP DATABASE [ERP_PROD] TO DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_DIFF_20261026.bak' WITH DIFFERENTIAL, CHECKSUM, COMPRESSION;# Log every 15 minutesBACKUP LOG [ERP_PROD] TO DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_LOG_20261027_1045.trn' WITH CHECKSUM, COMPRESSION;# Alternative: to a URL with a SAS credential created for that containerBACKUP DATABASE [ERP_PROD] TO URL = N'https://contosobkp.blob.core.windows.net/sql/ERP_PROD_FULL_20261024.bak' WITH CHECKSUM, COMPRESSION;# Check the file can be read and the checksums matchRESTORE VERIFYONLY FROM DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_FULL_20261024.bak' WITH CHECKSUM;The backup set on file 1 is valid.- One file per backup with date and time in the name. Always writing over the same
.bakwithINITleaves a single copy and no history. - If another tool (the backup platform's agent) also backs up the ERP, one of the two must use
COPY_ONLY; two differential chains on the same database end in a restore that can't find its full. VERIFYONLYchecks that the backup is readable, not that the database is healthy. Only a real restore and aDBCC CHECKDBon it tell you that.
3. Retention
| Type | On the share | Copy off site |
|---|---|---|
| Full | 5 weeks | One a month, 12 months (or what audit requires) |
| Differential | 14 days | No |
| Log | 14 days | As long as the full they depend on exists |
Clean-up is done by a SQL Server Agent job or the backup platform, never by someone deleting files by hand: a deleted log breaks the chain from that point on. Year-end close backups are labelled and kept apart, because finance or audit will ask for them years later.
4. The restore chain on the test server
To go back to 10:39 on Tuesday, the chain is: the last full before it, the last differential after that full and before 10:39, and every log from that differential up to the one containing 10:39.
RESTORE DATABASE [ERP_PROD_1039] FROM DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_FULL_20261024.bak' WITH MOVE N'ERP_PROD' TO N'E:\SQLData\ERP_PROD_1039.mdf', MOVE N'ERP_PROD_log' TO N'F:\SQLLog\ERP_PROD_1039.ldf', NORECOVERY, STATS = 10;RESTORE DATABASE [ERP_PROD_1039] FROM DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_DIFF_20261026.bak' WITH NORECOVERY;RESTORE LOG [ERP_PROD_1039] FROM DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_LOG_20261027_0015.trn' WITH NORECOVERY;# ... one RESTORE LOG per file, in order ...RESTORE LOG [ERP_PROD_1039] FROM DISK = N'\\srv-bkp01\sql$\ERP_PROD\ERP_PROD_LOG_20261027_1045.trn' WITH STOPAT = N'2026-10-27T10:39:00', RECOVERY;RESTORE LOG successfully processed 1874 pages in 0.412 seconds (35.532 MB/sec).- The logical names for
MOVEcome fromRESTORE FILELISTONLY FROM DISK = .... - While the database is in
NORECOVERYyou can keep applying backups;RECOVERYopens it and ends the chain. If you don't know the exact minute, restore the logs withSTANDBY = N'F:\SQLLog\ERP_PROD_1039.tuf'instead ofNORECOVERY: the database stays read-only between logs and you can query it to close in on the moment. - Write down how long it took, with a
DBCC CHECKDBat the end: that is your real RTO for the ERP. Repeat the test every quarter.
5. msdb history
SELECT TOP (8) bs.type, bs.backup_start_date, bs.backup_finish_date, bs.has_backup_checksums, bs.is_copy_only, bmf.physical_device_name FROM msdb.dbo.backupset bs JOIN msdb.dbo.backupmediafamily bmf ON bs.media_set_id = bmf.media_set_id WHERE bs.database_name = N'ERP_PROD' ORDER BY bs.backup_finish_date DESC;type backup_start_date backup_finish_date has_backup_checksums is_copy_only physical_device_name---- ------------------- ------------------- -------------------- ------------ ----------------------------------------------L 2026-10-27 10:45:00 2026-10-27 10:45:04 1 0 \\srv-bkp01\sql$\ERP_PROD\ERP_PROD_LOG_20261027_1045.trnI 2026-10-26 23:00:00 2026-10-26 23:06:41 1 0 \\srv-bkp01\sql$\ERP_PROD\ERP_PROD_DIFF_20261026.bakD 2026-10-24 22:00:00 2026-10-24 23:12:18 1 0 \\srv-bkp01\sql$\ERP_PROD\ERP_PROD_FULL_20261024.bakD is full, I differential and L log. With this query you build the list of files in the chain without hunting through the share by eye. msdb.dbo.restorehistory, on the test server, records every restore you run for the quarterly test.
6. The ticket: "someone deleted rows in the ERP at 10:40, can we get them back?"
Tuesday. A plant in another country reports that purchase order lines for one supplier have disappeared. The ERP is still in use at every site.
- Which "10:40": the plant's, not the server's. If the plant is an hour behind, for the server it was 11:40. Confirm with the user and with the ERP's audit log if it has one.
- Production is not restored: rolling the whole ERP back would wipe the work of every other plant since then. You restore a separate copy and recover only those rows.
- Secure the log: take a log backup now (without
NORECOVERY, production stays open) so the period of the deletion is in a backup and doesn't depend on the next scheduled one. - Restore on
srv-sql-test01the chain up to one minute before the deletion (section 4), under another database name. - Compare: the rows that are in the copy and not in production, by the table's key, in a query with the ERP consultant alongside.
- Put them back the ERP's way: with its import tool, with a script reviewed by the partner (watch out for
IDENTITYcolumns, triggers and related tables) or by re-entering them if there are few. What you don't do is a directINSERTthat nobody on the ERP side has validated. - Close with a cause: who deleted them, from which user and why they could. Often it is an excess permission in an ERP profile, not a technical fault.
Next steps
- The Level 3 PostgreSQL/MySQL/SQL Server guide covers day-to-day database administration.
- The Veeam Backup guide explains how the SQL server backup fits with the virtual machine backups.
- The Level 4 Backup and DR/BCP guide places the ERP within the continuity plan.
Is something missing, or different in your environment? Get in touch and I'll fix it.
Tech Career Lab · newsletter
¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.Found this useful? Every two weeks I send one practical idea for IT professionals: LinkedIn, projects, networking and hiring processes.
Apúntate y llévate las 33 ideasSign up and get the 33 ideas →