Секционирование, как способ обеспечения высокой доступности данных

by Alexey Knyazev 14. февраля 2013 00:51

Секционирование (partitions) - один из основных инструментов для обеспечения оптимальной работы с большим объёмом данных за счёт горизонтального масштабирования. Повышается управляемость и производительность, как модификации данных, так и запросы на выборку.
Кроме того, можно повысить производительность, применяя блокировки на уровне секций, а не всей таблицы. Это может уменьшить количество конфликтов блокировок для таблицы.

Но не все обращают внимание на ещё одну уникальную возможность, которую предоставляет секционирование, а именно: обеспечение высокой доступности данных. Сокращает время восстановления после сбоев.

Для демонстрации создадим тестовую БД:

use master;
go
if db_id( 'TestDB' ) is not null
drop database TestDB;
go
create database TestDB 
    on ( name = 'TestDB',     filename = 'c:\temp\TestDB.mdf' )
log on ( name = 'TestDB_log', filename = 'c:\temp\TestDB.ldf' );
go

Теперь к нашей новой БД добавим несколько файловых групп, каждая из которых будет состоять из одного вторичного файла данных (*.ndf):

alter database TestDB add filegroup FG1;
alter database TestDB add filegroup FG2;
alter database TestDB add filegroup FG3;
alter database TestDB add filegroup FG4;
alter database TestDB add filegroup FG5;
alter database TestDB add filegroup FG6;
alter database TestDB add filegroup FG7;
alter database TestDB add filegroup FG8;
alter database TestDB add filegroup FG9;
alter database TestDB add filegroup FG10;
alter database TestDB add filegroup FG11;
alter database TestDB add filegroup FG12;
go

alter database TestDB
add file  ( name = 'fg1',  filename = 'c:\temp\fg1.ndf'  ) to filegroup FG1;
alter database TestDB
add file  ( name = 'fg2',  filename = 'c:\temp\fg2.ndf'  ) to filegroup FG2;
alter database TestDB
add file  ( name = 'fg3',  filename = 'c:\temp\fg3.ndf'  ) to filegroup FG3;
alter database TestDB
add file  ( name = 'fg4',  filename = 'c:\temp\fg4.ndf'  ) to filegroup FG4;
alter database TestDB
add file  ( name = 'fg5',  filename = 'c:\temp\fg5.ndf'  ) to filegroup FG5;
alter database TestDB
add file  ( name = 'fg6',  filename = 'c:\temp\fg6.ndf'  ) to filegroup FG6;
alter database TestDB
add file  ( name = 'fg7',  filename = 'c:\temp\fg7.ndf'  ) to filegroup FG7;
alter database TestDB
add file  ( name = 'fg8',  filename = 'c:\temp\fg8.ndf'  ) to filegroup FG8;
alter database TestDB
add file  ( name = 'fg9',  filename = 'c:\temp\fg9.ndf'  ) to filegroup FG9;
alter database TestDB
add file  ( name = 'fg10',  filename = 'c:\temp\fg10.ndf'  ) to filegroup FG10;
alter database TestDB
add file  ( name = 'fg11',  filename = 'c:\temp\fg11.ndf'  ) to filegroup FG11;
alter database TestDB
add file  ( name = 'fg12',  filename = 'c:\temp\fg12.ndf'  ) to filegroup FG12;

Теперь наша БД состоит из 13 файловых группы: первичной и 12 вторичных

use TestDB;
go
select name, data_space_id from sys.filegroups;
go




Файлы:

use TestDB;
go
select  file_id
      , data_space_id
      , name
      , physical_name
      , state_desc
from sys.database_files
order by data_space_id;
go




Все эти подготовки были сделаны не случайно, теперь мы создадим функцию и схему секционирования. При этом мы будем секционировать нашу будущую "важную" таблицу с данными за 2012 год по месяцу. Каждая отдельная секция будет располагаться в своей выделенной файловой группе.

use TestDB;
go
create partition function pf_dt (datetime)
as range right
for values ( '20120101', '20120201', '20120301', '20120401' 
           , '20120501', '20120601', '20120701', '20120801' 
           , '20120901', '20121001', '20121101', '20121201'
           , '20130101'
           );
go

create partition scheme ps_dt
as partition pf_dt
to ( [primary], FG1, FG2, FG3, FG4, FG5, FG6, FG7, FG8, FG9, FG10, FG11, FG12, [primary] );
go

Обратите ваше внимание, что все данные, которые будут с датой < 2012 и > 2012 года расположатся в файловой группе primary.

Ну, а теперь создадим нашу наиважнейшую таблицу, которая будет содержать информацию о продажах за 2012 год. При этом к этим данным выдвигаются очень серьёзные бизнес-требования в плане доступности.

if object_id ( N'dbo.sales', 'U' ) is not null
drop table dbo.sales;
go

create table dbo.sales 
( id int identity
, val varchar(255) default ( replicate ( 'A', 255) )
, price money default ( rand() )
, dt datetime 
) on ps_dt( dt );
go

insert into dbo.sales ( dt )
values  ( '20120101' )
      , ( '20120201' )
      , ( '20120301' )
      , ( '20120401' )
      , ( '20120501' )
      , ( '20120601' )
      , ( '20120701' )
      , ( '20120801' )
      , ( '20120901' )
      , ( '20121001' )
      , ( '20121101' )
      , ( '20121201' );
go 10000

В результате у нас таблица с 14 секциями, 12 из которых содержат данные...в нашем случаи по 10000 строк:

select partition_number, rows
from sys.partitions
where object_id = object_id ( N'dbo.sales', 'U' );




Т.к. у нас каждая секция хранится в отдельной файловой группе, то мы можем создавать резервные копии не только всей БД, но и отдельно для каждой файловой группы.
Следующим скриптом мы создадим 12 резервных копий файловых групп:

backup database TestDB
filegroup = 'FG1' to disk = 'C:\temp\backup\fg1.bak';

backup database TestDB
filegroup = 'FG2' to disk = 'C:\temp\backup\fg2.bak';

backup database TestDB
filegroup = 'FG3' to disk = 'C:\temp\backup\fg3.bak';

backup database TestDB
filegroup = 'FG4' to disk = 'C:\temp\backup\fg4.bak';

backup database TestDB
filegroup = 'FG5' to disk = 'C:\temp\backup\fg5.bak';

backup database TestDB
filegroup = 'FG6' to disk = 'C:\temp\backup\fg6.bak';

backup database TestDB
filegroup = 'FG7' to disk = 'C:\temp\backup\fg7.bak';

backup database TestDB
filegroup = 'FG8' to disk = 'C:\temp\backup\fg8.bak';

backup database TestDB
filegroup = 'FG9' to disk = 'C:\temp\backup\fg9.bak';

backup database TestDB
filegroup = 'FG10' to disk = 'C:\temp\backup\fg10.bak';

backup database TestDB
filegroup = 'FG11' to disk = 'C:\temp\backup\fg11.bak';

backup database TestDB
filegroup = 'FG12' to disk = 'C:\temp\backup\fg12.bak';

Теперь сэмулируем аварию, для этого внесем изменения в одну из файловых групп через любой текстовый редактор. Но прежде необходимо отключить нашу БД (sp_detach_db) или остановить SQL Server, т.к. иначе файлы БД нельзя отредактировать.

use master;
go
exec sp_detach_db 'TestDB';
go

Теперь внесём изменения в один из файлов БД, например в файл C:\temp\fg7.ndf. В качестве редактора я использую Far Manager.



Нам достаточно изменить всего один символ, чтобы наша БД не прошла проверку контрольной суммы. За эту проверку отвечает параметр БД PAGE_VERIFY = CHECKSUM (значение по умолчанию и если вы его не меняли, то вам не стоит о нём беспокоиться), более подробно об этом можно прочитать по ссылке - http://msdn.microsoft.com/ru-ru/library/bb522682.aspx.

Теперь подключим нашу базу:

use master;
go
exec sp_attach_db 'TestDB'
                , 'C:\temp\TestDB.mdf'
                , 'C:\temp\TestDB.ldf'
                , 'C:\temp\fg1.ndf'
                , 'C:\temp\fg2.ndf'
                , 'C:\temp\fg3.ndf'
                , 'C:\temp\fg4.ndf'
                , 'C:\temp\fg5.ndf'
                , 'C:\temp\fg6.ndf'
                , 'C:\temp\fg7.ndf'
                , 'C:\temp\fg8.ndf'
                , 'C:\temp\fg9.ndf'
                , 'C:\temp\fg10.ndf'
                , 'C:\temp\fg11.ndf'
                , 'C:\temp\fg12.ndf';
go

После подключения БД, попытаемся считать данные из файловой группы, которую мы отредактировали.

select * from dbo.sales
where $partition.pf_dt(dt) = 8;

В результате мы получим ошибку:

SQL Server detected a logical consistency-based I/O error: incorrect checksum (expected: 0x95830087; actual: 0x959e8087). It occurred during a read of page (9:8) in database ID 5 at offset 0x00000000010000 in file 'C:\temp\fg7.ndf'. Additional messages in the SQL Server error log or system event log may provide more detail. This is a severe error condition that threatens database integrity and must be corrected immediately. Complete a full database consistency check (DBCC CHECKDB). This error can be caused by many factors; for more information, see SQL Server Books Online.

При этом данные из других файловых групп доступны для работы

select count(*) from dbo.sales
where $partition.pf_dt(dt) != 8;

--110000

Т.е., не смотря на битые данные в одной из секций, мы продолжаем работать с остальными данными в обычном режиме. Теперь попытаемся восстановить нашу "битую" файловую группу в режиме ONLINE, т.е. без остановки работы с другими данными.
Начинаем всё с переключения нашей ФГ (файловой группы) в режим OFFLINE

alter database TestDB 
modify file ( name = FG7, offline  );
go

Теперь посмотрим статусы файловых групп:

select  file_id
      , data_space_id
      , name
      , physical_name
      , state_desc
from sys.database_files
order by data_space_id;




Теперь, при обращении к нашей ФГ, мы получим уже другую ошибку

select count(*) from dbo.sales
where $partition.pf_dt(dt) = 8;

One of the partitions of index '' for table 'dbo.sales'(partition ID 72057594039500800) resides on a filegroup ("FG7") that cannot be accessed because it is offline, restoring, or defunct. This may limit the query result.

Далее выполним само восстановление FG7

use master;
go
restore database TestDB
filegroup = 'FG7'
from disk = 'C:\temp\backup\fg7.bak'
with norecovery;
go



Статус нашей ФГ изменился с OFFLINE на RESTORING.

Последним шагом необходимо сделать копию журнала транзакций и восстановить его с параметром RECOVERY.

backup log TestDB
to disk = 'C:\temp\backup\TestDB_log.bak';
go

restore log TestDB
from disk = 'C:\temp\backup\TestDB_log.bak'
with recovery;
go

После этого статус ФГ сменится на ONLINE и все данные нашей таблицы вновь доступны. Мало того, что на протяжении всего этого времени вся наша БД была в полном доступе для пользователей (за исключением данных в FG7), так мы ещё и восстановление произвели в разы быстрее, чем если бы производили поднятие базы из полной резервной копии. Поэтапное восстановление более подробно описано по ссылке - http://msdn.microsoft.com/ru-ru/library/ms177425.aspx

Но теперь мы проведём более серьёзный тест и удалим одну из файловых групп, но прежде сделаем новую резервную копию нашей файловой группы, которую будем удалять:

backup database TestDB
filegroup = 'FG7' to disk = 'C:\temp\backup\fg7.bak'
with init;

Теперь остановим службу SQL Server, удалим файл C:\temp\fg7.ndf и вновь запустим службу SQL Server.

select name, state_desc from sys.databases
where name = 'TestDB';



Обратимся за подробностями к логу

exec xp_readerrorlog;



И так: наша база данных повреждена т.к. нет доступа к одному из файлов. Первым делом нужно вернуть БД в оперативный доступ. Для этого необходимо перевести в OFFLINE одну ФГ, файл которой "пропал", а всю базу перевести в ONLINE-режим.

alter database TestDB 
modify file ( name = FG7, offline  );
go

alter database TestDB set online;
go 



Далее восстановление очень похоже на то, что мы делали ранее

use master;
go
restore database TestDB
filegroup = 'FG7'
from disk = 'C:\temp\backup\fg7.bak'
with norecovery;
go

backup log TestDB
to disk = 'C:\temp\backup\TestDB_log_new.bak';
go

restore log TestDB
from disk = 'C:\temp\backup\TestDB_log_new.bak'
with recovery;
go

Вот и всё!

Tags: , ,

SQL Server

Комментарии (6) -

Евгений
Евгений Russia
28.02.2013 4:32:45 #

Алексей, спасибо, ваши посты весьма полезны.. Жаль, что rss подписка не работает.

Reply

Dmitriy
Dmitriy Russia
02.04.2013 5:30:04 #

Алексей, спасибо за статьи, всегда читаю с интересом и постоянно подчеркиваю для себя что то новое.

Reply

Katya
Katya Russia
18.04.2013 14:45:23 #

А это как-то сочетается с кластерными индексами?

Reply

Alexey Knyazev
Alexey Knyazev Russia
18.04.2013 16:48:07 #

А это как-то сочетается с кластерными индексами?
не совсем понял вашего вопроса...уточните, что вас интересует?

Reply

Arty
Arty Russia
23.06.2013 7:10:42 #

Алексей, огромное спасибо за статью

Reply

Arty
Arty Russia
23.06.2013 7:36:04 #

Алексей, отличная статья спасибо.

Reply

Добавить комментарий

  Country flag

biuquote
  • Комментарий
  • Предпросмотр
Loading