Разработка приложения - это постоянное изменение. Изменение кода, реквайрементов, и естественно, изменение базы данных, как структуры так и данных.
Проблема
Каждый разработчик сталкивается с данной проблемой чуть ли не каждый день. Что бы понять проблему рассмотрим примеры:
Решение
Идеальным решением является апдейт скрипты. Предлагаю Вам рассмотреть следующую версию решения, которое работает на MS SQL 2005+.
Программист1 добавляет таблицу [Client]. Как результат получаем скрипт 1_0_0_x.sql (ВАЖНО! первая строка в файле скрипта должна быть SET XACT_ABORT ON, эта инструкция говорит, что бы сиквел сервер откатывал транзакцию при любой ошибке):
Программист1 добавляет колонку [Description] в таблицу [Client] и одновременно Программист2 добавляет колонку [CreatedDate] в таблицу [Client]. После мержа (merge) в скрипте 1_0_0_x.sql добавится 2 апдейта:
Проблема
Каждый разработчик сталкивается с данной проблемой чуть ли не каждый день. Что бы понять проблему рассмотрим примеры:
- Работая в команде, иногда нескольким разработчика нужно менять БД одновременно, как изменять базу данных, что бы каждый мог менять БД независимо друг от друга?
- Какая гарантия, что версия базы данных соответствует коду приложения? Как узнать Ивану Дураку, что Павлик Морозов поменял БД?
- Как менять БД, которая уже в продакшине?
Решение
Идеальным решением является апдейт скрипты. Предлагаю Вам рассмотреть следующую версию решения, которое работает на MS SQL 2005+.
Программист1 добавляет таблицу [Client]. Как результат получаем скрипт 1_0_0_x.sql (ВАЖНО! первая строка в файле скрипта должна быть SET XACT_ABORT ON, эта инструкция говорит, что бы сиквел сервер откатывал транзакцию при любой ошибке):
SET XACT_ABORT ON -- BEGIN UPDATE: 1.0.0.x PROJ-1 Create Client DECLARE @UpdateId NVARCHAR(MAX); SET @UpdateId = '1.0.0.x PROJ-1'; IF [dbo].[IsUpdateExist](@UpdateId) = 0 BEGIN BEGIN TRAN @UpdateId; EXEC dbo.sp_executesql @statement = N' CREATE TABLE [dbo].[Client]( [Id] [int] IDENTITY(1,1) NOT NULL, [Name] [nvarchar](255) NOT NULL )' EXEC [dbo].CommitUpdate @UpdateId; COMMIT TRAN @UpdateId; END GO -- END UPDATE: 1.0.0.x PROJ-1 Create Client
Программист1 добавляет колонку [Description] в таблицу [Client] и одновременно Программист2 добавляет колонку [CreatedDate] в таблицу [Client]. После мержа (merge) в скрипте 1_0_0_x.sql добавится 2 апдейта:
-- BEGIN UPDATE: 1.0.0.x PROJ-2 Add Client Description DECLARE @PrevUpdateId NVARCHAR(MAX); DECLARE @UpdateId NVARCHAR(MAX); SET @PrevUpdateId = '1.0.0.x PROJ-1'; SET @UpdateId = '1.0.0.x PROJ-2'; IF [dbo].[IsUpdateExist](@PrevUpdateId) = 1 AND [dbo].[IsUpdateExist](@UpdateId) = 0 BEGIN BEGIN TRAN @UpdateId; EXEC dbo.sp_executesql @statement = N' ALTER TABLE dbo.Client ADD Description nvarchar(MAX) NULL' EXEC [dbo].CommitUpdate @UpdateId; COMMIT TRAN @UpdateId; END GO -- END UPDATE: 1.0.0.x PROJ-2 Add Client Description -- BEGIN UPDATE: 1.0.0.x PROJ-3 Add Client Creation Date DECLARE @PrevUpdateId NVARCHAR(MAX); DECLARE @UpdateId NVARCHAR(MAX); SET @PrevUpdateId = '1.0.0.x PROJ-1'; SET @UpdateId = '1.0.0.x PROJ-3'; IF [dbo].[IsUpdateExist](@PrevUpdateId) = 1 AND [dbo].[IsUpdateExist](@UpdateId) = 0 BEGIN BEGIN TRAN @UpdateId; EXEC dbo.sp_executesql @statement = N' ALTER TABLE dbo.Client ADD CreatedDate datetime NULL' EXEC [dbo].CommitUpdate @UpdateId; COMMIT TRAN @UpdateId; END GO -- END UPDATE: 1.0.0.x PROJ-3 Add Client Creation Date
ВАЖНО! Внутри апдейта нельзя использовать GO, вместо этого необходимо использовать хранимку EXEC dbo.sp_executesql @statement = N'...'
НО, что бы это все заработало нам нужен следующий скрипт SetupDatabaseVersioning.sql, который содержит [dbo].[IsUpdateExist] и [dbo].CommitUpdate.
-- BEGIN: Sql updates management IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[UpdateLog]') AND type in (N'U')) CREATE TABLE [dbo].[UpdateLog]( [Key] [nvarchar](255) NOT NULL, [Timestamp] [datetime] NOT NULL CONSTRAINT [DF_UpdateLog_Timestamp] DEFAULT (getdate()), CONSTRAINT [PK_UpdateLog] PRIMARY KEY CLUSTERED ( [Key] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[IsUpdateExist]') AND type in (N'FN')) EXEC dbo.sp_executesql @statement = N' CREATE FUNCTION [dbo].[IsUpdateExist] (@UpdateKey NVARCHAR(MAX)) RETURNS BIT AS BEGIN DECLARE @Count INT SELECT @Count = COUNT(*) FROM UpdateLog WHERE [Key] = @UpdateKey RETURN CAST(@Count AS BIT) END ' GO IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[CommitUpdate]') AND type in (N'P', N'PC')) EXEC dbo.sp_executesql @statement = N' CREATE PROCEDURE [dbo].[CommitUpdate] @UpdateKey NVARCHAR(MAX) AS BEGIN SET NOCOUNT ON; IF [dbo].[IsUpdateExist](@UpdateKey) = 0 INSERT INTO [UpdateLog]([Key]) VALUES(@UpdateKey) END ' GO -- END: Sql updates management
Пойдем далее, сочиним пакетный файл ApplyAllUpdates.bat, что бы применить все апдейты.
cd /d %~dp0 set server=%1% set databaseName=%2% if "%databaseName%" == "" set databaseName=MyDatabase if "%server%" == "" set server=(local) sqlcmd -S %server% -Q "USE %databaseName% EXEC sp_changedbowner 'sa'" sqlcmd -S %server% -d %databaseName% -i "SetupDatabaseVersioning.sql" -v database=%databaseName% sqlcmd -S %server% -d %databaseName% -i "1_0_0_x.sql" -v database=%databaseName% sqlcmd -S %server% -d %databaseName% -i "1_1_0_x.sql" -v database=%databaseName% sqlcmd -S %server% -d %databaseName% -i "1_2_0_x.sql" -v database=%databaseName%
P.S.: Версию базы данных можно вычислить, она соответсвует количеству записей в таблице [UpdateLog]. Данная таблица содержит список всех апдейтов, которые были выполнены. Данное число можно хранить в коде приложения, и в случае несоответсвия версии БД переводить приложение в нерабочее состояние.
Комментарии
Отправить комментарий