Разработка приложения - это постоянное изменение. Изменение кода, реквайрементов, и естественно, изменение базы данных, как структуры так и данных.
Проблема
Каждый разработчик сталкивается с данной проблемой чуть ли не каждый день. Что бы понять проблему рассмотрим примеры:
Решение
Идеальным решением является апдейт скрипты. Предлагаю Вам рассмотреть следующую версию решения, которое работает на 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]. Данная таблица содержит список всех апдейтов, которые были выполнены. Данное число можно хранить в коде приложения, и в случае несоответсвия версии БД переводить приложение в нерабочее состояние.
Комментарии
Отправить комментарий