Публикация

Откройте MSSQL Management Studio и выполните следующий запрос:

RESTORE HEADERONLY

FROM DISK = N'c:\temp\dbMyDatabase.bak' --подставить путь к бэкапу

WITH NOUNLOAD;

Затем посмотрите содержимое колонки DatabaseVersion. Пример:

databaseversion

Затем сверьтесь с таблицей: http://weblogs.sqlteam.com/dang/archive/2011/07/17/internal-sql-server-d...

В данному случае у нас параметр равен 661, то есть, бэкап сделан на версии SQL Server 2008

Нравится

Поделиться

0 комментариев
Показать все комментарии

Ничто так не "убивает" базу, как "плохое" индексирование (С)

Создать правильные индексы - это только половина дела. Нужно еще и правильно ими управлять.

В процессе работы с базой, особенно, если данные в ней довольно часто модифицируются/добавляются/удаляются, со временем индексы приходят в некоторую "негодность". Увеличивается их "фрагментарность", ухудшается их влияние на скорость исполнения запросов к БД.
Оптимальным считается, когда уровень фрагментации индекса не превышает 10%, но для поддержания такого показателя необходимо проводить периодическую их дефрагментацию и реорганизацию.
(подробнее про реорганизацию и дефрагментацию)

Основным инструментом в процессе управления фрагментацией индексов выступает функция sys.dm_db_index_physical_stats (подробнее)

Для определения списка индексов с уровнем фрагментарности выше оптимальных 10% в своей работе я воспользовалась вот таким запросом:

DECLARE @db_name varchar(50) = N'db_name',
                @table_name varchar(250) = N'db_name.dbo.tbl_name'

SELECT  IndStat.database_id,
                IndStat.object_id,
                QUOTENAME(s.name) + '.' + QUOTENAME(o.name) AS [object_name],
                IndStat.index_id,
                QUOTENAME(i.name) AS index_name,
                IndStat.avg_fragmentation_in_percent,
                IndStat.partition_number,
                (SELECT count (*) FROM sys.partitions p
                        WHERE p.object_id = IndStat.object_id AND p.index_id = IndStat.index_id) AS partition_count
FROM sys.dm_db_index_physical_stats
    (DB_ID(@db_name), OBJECT_ID(@table_name), NULL, NULL , 'LIMITED') AS IndStat
        INNER JOIN sys.objects AS o ON (IndStat.object_id = o.object_id)
        INNER JOIN sys.schemas AS s ON s.schema_id = o.schema_id
        INNER JOIN sys.indexes i ON (i.object_id = IndStat.object_id AND i.index_id = IndStat.index_id)
WHERE IndStat.avg_fragmentation_in_percent > 10 AND IndStat.index_id > 0

Если указать @table_name = NULL, тогда мы получим данные по всем таблицам указанной базы.
Если указать и @db_name = NULL - получим информацию по всем таблицам всех баз.

Естественно, для выполнения этого запроса нужно обладать некоторыми правами:

  • CONTROL на специфический объект БД.
  • VIEW DATABASE STATE для получения информации обо всех объектах определенной БД (@object_id = NULL).
  • VIEW SERVER STATE - для получения информации обо всех базах сервера (@database_id = NULL).

Так же перед использованием желательно обновить статистику БД.

Вышеприведенный запрос дает только справочную информацию. Но на основании функции sys.dm_db_index_physical_stats можно построить скрипт или хранимую процедуру, которая будет не только определять список индексов, нуждающихся в перестройке, но и будет сама проводить эту операцию.
Один из вариантов такого скрипта приведен ниже (проводит реорганизацию или перестройку(оффлайн) индексов таблиц текущей базы в зависимости от степени фрагментации):

USE [DATABASE];
GO

SET NOCOUNT ON;
DECLARE @objectid int;
DECLARE @indexid int;
DECLARE @partitioncount bigint;
DECLARE @schemaname nvarchar(130);
DECLARE @objectname nvarchar(130);
DECLARE @indexname nvarchar(130);
DECLARE @partitionnum bigint;
DECLARE @partitions bigint;
DECLARE @frag float;
DECLARE @command nvarchar(4000);
DECLARE @dbid smallint;

-- Выбираем индексы с уровнем фрагментации выше 10%
-- Определяем текущую БД

SET @dbid = DB_ID();
SELECT
    [object_id] AS objectid,
    index_id AS indexid,
    partition_number AS partitionnum,
    avg_fragmentation_in_percent AS frag, page_count
INTO #work_to_do
FROM sys.dm_db_index_physical_stats (@dbid, NULL, NULL , NULL, N'LIMITED')
WHERE avg_fragmentation_in_percent > 10.0  
AND index_id > 0 -- игнорируем heap
AND page_count > 25; -- игнорируем маленькие таблицы

-- объявляем курсор для списка обрабатываемых partition
DECLARE partitions CURSOR FOR SELECT objectid,indexid, partitionnum,frag FROM #work_to_do;

OPEN partitions;

-- цикл по partition
WHILE (1=1)
BEGIN
FETCH NEXT
FROM partitions
INTO @objectid, @indexid, @partitionnum, @frag;
IF @@FETCH_STATUS 0 BREAK;
SELECT @objectname = QUOTENAME(o.name), @schemaname = QUOTENAME(s.name)
FROM sys.objects AS o
JOIN sys.schemas AS s ON s.schema_id = o.schema_id
WHERE o.object_id = @objectid;

SELECT @indexname = QUOTENAME(name)
FROM sys.indexes
WHERE object_id = @objectid AND index_id = @indexid;
SELECT @partitioncount = count (*)
FROM sys.partitions
WHERE object_id = @objectid AND index_id = @indexid;

-- 30% считаем пределом для определения типа обновления индекса.
IF @frag 30.0
    SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REORGANIZE';
IF @frag >= 30.0
    SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD';
IF @partitioncount > 1
    SET @command = @command + N' PARTITION=' + CAST(@partitionnum AS nvarchar(10));

EXEC (@command);
PRINT N'Выполнено: ' + @command;
END;

CLOSE partitions;
DEALLOCATE partitions;

-- удаляем временную таблицу
DROP TABLE #work_to_do;
GO

Еще посмотреть примеры скриптов/хранимок можно тут или вот тут.

Операцию по устранению дефрагментации индексов рекомендуется проводить регулярно (например, раз в неделю/месяц - в зависимости от величины операций модификации над хранимыми данными). Индексы, за состоянием которых не следят, могут очень существенно "просадить" производительность БД при исполнении запросов.

Нравится

Поделиться

0 комментариев
Показать все комментарии

Ничто не оптимизирует скорость работы базы так, как правильно подобранные индексы (С)

Многим, кто сталкивался с промышленной эксплуатацией баз данных определенных (скажем так, очень больших) размеров, приходилось проводить кропотливый анализ запросов на предмет возможной оптимизации скорости их выполнения.

Как оказалось, сама СУБД MSSQL версии 2005 и выше содержит механизм, грамотное использование которого может очень сильно облегчить работу по поиску некоторых узких мест.

Это sys.dm_db_missing_index_group_stats и связанные с ней функции (подробнее >>)
Данные для них формируются на основании статистики запросов к базе данных, и потому являются довольно хорошей информацией, от которой можно оттолкнуться при оптимизации.

Я не буду приводить тут подробный анализ ее использования, а только приведу запрос, который очень помог мне лично:

SELECT
        [Impact] = (avg_total_user_cost * avg_user_impact) * (user_seeks + user_scans),
        [TABLE] = [statement],
        [CreateIndexStatement] = 'CREATE NONCLUSTERED INDEX ix_'
                        + sys.objects.name COLLATE DATABASE_DEFAULT
                        + '_'
                        + REPLACE(REPLACE(REPLACE(ISNULL(mid.equality_columns,'')+ISNULL(mid.inequality_columns,''), '[', ''), ']',''), ', ','_')
                        + ' ON '
                        + [statement]
                        + ' ( ' + IsNull(mid.equality_columns, '')
                        + CASE
                                WHEN mid.inequality_columns IS NULL THEN ''
                                ELSE
                                        (CASE
                                                WHEN mid.equality_columns IS NULL THEN ''
                                                ELSE ','
                                         END)
                                        + mid.inequality_columns
                                END
                        + ' ) '
                        + CASE
                                WHEN mid.included_columns IS NULL THEN ''
                                        ELSE 'INCLUDE (' + mid.included_columns + ')'
                                END
                        + ';',
        mid.equality_columns,
        mid.inequality_columns,
        mid.included_columns
FROM sys.dm_db_missing_index_group_stats AS migs
        INNER JOIN sys.dm_db_missing_index_groups AS mig ON migs.group_handle = mig.index_group_handle
        INNER JOIN sys.dm_db_missing_index_details AS mid ON mig.index_handle = mid.index_handle
        INNER JOIN sys.objects WITH (nolock) ON mid.OBJECT_ID = sys.objects.OBJECT_ID
WHERE   (migs.group_handle IN
                (SELECT TOP (500) group_handle
                FROM sys.dm_db_missing_index_group_stats WITH (nolock)
                ORDER BY (avg_total_user_cost * avg_user_impact) * (user_seeks + user_scans) DESC))
                AND OBJECTPROPERTY(sys.objects.OBJECT_ID, 'isusertable') = 1
ORDER BY [Impact] DESC , [CreateIndexStatement] DESC

Естественно, для выполнения этого запроса нужно обладать правом VIEW SERVER STATE или любым другим правом, включающим в себя VIEW SERVER STATE.
Так же перед использованием желательно обновить статистику БД.

P.S. Еще можно почитать (и даже посмотреть видео) вот тут.

P.S.2 Определяем, как часто "пользуются" индексами:

SELECT OBJECT_NAME(S.[OBJECT_ID]) AS [OBJECT NAME],
 I.[NAME] AS [INDEX NAME],
 USER_SEEKS,
 USER_SCANS,
 USER_LOOKUPS,
 USER_UPDATES
FROM SYS.DM_DB_INDEX_USAGE_STATS AS S
 INNER JOIN SYS.INDEXES AS I
 ON I.[OBJECT_ID] = S.[OBJECT_ID]
 AND I.INDEX_ID = S.INDEX_ID

Нравится

Поделиться

0 комментариев
Показать все комментарии

Часто случается, что нам в хранимках БД необходимо найти упоминания о каких-либо константах, которые, например, собираетесь удалить. И будет хорошо, если вы помните, где именно их использовали, а что если нет?

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

SELECT ROUTINE_NAME, ROUTINE_DEFINITION
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_DEFINITION LIKE '%Your text for search%'
--AND ROUTINE_TYPE='PROCEDURE'
--OR ROUTINE_TYPE='FUNCTION'

Следующий запрос делает выборку из всех объектов, существующих в БД:

SELECT [type], [name], OBJECT_DEFINITION([object_id])
FROM sys.objects
WHERE [type] IN ('P','TR','FN') AND OBJECT_DEFINITION([object_id]) LIKE N'%Your text for search%';

Но sys.objects не показывает триггеры DDL, так как они не принадлежат области схемы. Все триггеры (DML и DDL) найдены в таблице sys.triggers, которая поддерживает смешанные правила имен для различных видов триггеров. (Материал с MSDN)

Какие существуют типы объектов:
AF = Aggregate function (CLR)
C = CHECK constraint
D = DEFAULT (constraint or stand-alone)
F = FOREIGN KEY constraint
FN = SQL scalar function
FS = Assembly (CLR) scalar-function
FT = Assembly (CLR) table-valued function
IF = SQL inline table-valued function
IT = Internal table
P = SQL Stored Procedure
PC = Assembly (CLR) stored-procedure
PG = Plan guide
PK = PRIMARY KEY constraint
R = Rule (old-style, stand-alone)
RF = Replication-filter-procedure
S = System base table
SN = Synonym
SO = Sequence object
SQ = Service queue
TA = Assembly (CLR) DML trigger
TF = SQL table-valued-function
TR = SQL DML trigger
TT = Table type
U = Table (user-defined)
UQ = UNIQUE constraint
V = View
X = Extended stored procedure

И последний способ - это поиск по таблице syscomments, которая содержит записи для всех представлений, правил, значений по умолчанию, триггеров, ограничений CHECK и DEFAULT, а также для всех хранимых процедур в базе данных.

SELECT DISTINCT so.name, sc.TEXT
FROM syscomments sc
INNER JOIN sysobjects so ON sc.id=so.id
WHERE sc.TEXT LIKE '%Your text for search%'

------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------

По просьбе Артёма написана процедура, возвращающая список объектов (название и тип), содержащих указанный текст.

CREATE PROCEDURE [dbo].[tsp_GetObjectsContainingText]
(
        @Text VARCHAR(Max),
        @Types VARCHAR(250) = 'P,TR,FN,TF'
)
AS
BEGIN
        SET NOCOUNT ON;
                       
        SELECT [name], [type_desc]
        FROM sys.objects
        WHERE EXISTS (
                SELECT d.Part FROM dbo.fn_ParseString(@Types, ',') d
                WHERE d.Part COLLATE  Cyrillic_General_CI_AI = [type] COLLATE  Cyrillic_General_CI_AI
        ) AND OBJECT_DEFINITION([object_id]) LIKE '%' + @Text + '%';
               
END

2-й входящий параметр Types по умолчанию = 'P,TR,FN,TF'.

Также для работы процедуры необходима функция fn_ParseString. Ниже её реализация:

CREATE FUNCTION [dbo].[fn_ParseString] (
        @ParseString nvarchar(4000), @Delimiter nvarchar(100))
returns @Result TABLE (
        [Line] int NOT NULL identity(1, 1),
        [Part] nvarchar(4000)
)
AS
begin
        declare @Part nvarchar(4000)
        declare @INDEX int
        SET @Delimiter = '%' + @Delimiter + '%'
        while (@ParseString > '')
        begin
                SET @INDEX = patindex(@Delimiter, @ParseString)
          IF (@INDEX > 0)
          begin
                SET @Part = substring(@ParseString, 1, @INDEX -1)
                        INSERT INTO @Result SELECT rtrim(ltrim(@Part))
                SET @ParseString = substring(@ParseString, @INDEX +1, len(@ParseString))
          end else
          begin
            SET @Part = substring(@ParseString, 1, len(@ParseString))
                INSERT INTO @Result SELECT rtrim(ltrim(@Part))
            break
          end
        end
  RETURN
end

Нравится

Поделиться

4 комментария

Максим, спасибо за ценную информацию!
Я бы добавил в список поиска также и

"Караша Максим Юрьевич" написал:
TF = SQL table-valued-function

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

Да, присоединяюсь к пожеланиям Артёма.
Нужно реализовать это всё в базовом функционале TSAdmin - в окне Greap Search!

Да, чтоб разработчики этим пользовались, лучше разместить в уже известном месте, в GREP-search

Для Оракла не нашёл корректного поиска.
Вот как вариант:

  select DISTINCT user_objects.object_name, user_objects.object_type from user_objects 
  JOIN sys.user_source ON sys.user_source.name = user_objects.object_name
  WHERE user_objects.object_type in ('PROCEDURE','FUNCTION','TRIGGER')
  AND sys.user_source.Text like '%text%'

Но недостатки запроса в том, что в таблице sys.user_source тело хранимок разбито на много записей, содержащихся в нескольких рядках.
Например небольшой цикл может занимать несколько записей:
1. BEGIN
2. LOOP
3. -- do something with the data
4. END LOOP;

И уже поиск по условию

sys.user_source.Text like '%BEGIN%LOOP%'

ничего не вернёт.

Может кто знает более универсальный вариант?

Показать все комментарии

Понедельник начинается в субботу, а MSSQL мне не верит! У финансистов и бухгалтеров это действительно так. Например, если день сдачи налогового отчёта приходится на субботу, это значит, что у них есть на написание суббота, воскресенье и половина понедельника. Перевести деньги в выходные тоже не получается: то ли банковские компьютеры отдыхают по выходным, то ли просто выходной у бабушки, что проверяет все электронные платежи на деревянных счётах. Раньше следующей недели денег не получишь.

Пришлось вспомнить эту особенность взаимоотношений времени и денег, когда понадобилось построить понедельный отчёт по ДДС, что расшифровывается как «Движение Денежных Средств». Если некое движение приходится на субботу или воскресенье – его следует отнести уже к следующей неделе. Ведь в действительности оно осуществится не раньше понедельника. Отчёт строился одним запросом на MSSQL2005, расстояние в неделях от текущей даты я считал при помощи функции datediff. Казалось бы, чего проще: на время сессии дать команду SET DATEFIRST, установив первым днём субботу…

А не тут-то было! Не поверил своим глазам: на datediff эта команда не оказала никакого влияния. Даже пример написал, думал всем показать своё открытие. Мол, ай да я, нашёл такую ошибку в MSSQL2005!

/*
Если сегодня установить первым днём недели,
то вчера будет считаться днём предыдущей недели, а завтра - текущей.
Для datepart это так,
для datediff - нет!
*/


declare @OldDatefirst int
declare @NewDatefirst int
declare @StartDate datetime
declare @EndDate  datetime

SET @OldDatefirst = @@Datefirst -- запоминаем день недели
SET @NewDatefirst = datepart(dw, getdate()) -- неделя начинается сегодня

SET @StartDate = getdate() - 1 -- вчера
SET @EndDate = getdate() + 1 -- завтра

SET DATEFIRST @NewDatefirst -- установили сегодняшний день первым днём недели

--по идее, вчера и завтра должны попадать в разные недели

SELECT @StartDate, @EndDate,      -- сами даты
datepart(ww, @StartDate), datepart(ww, @EndDate), -- номера недель в году отличаются
datediff(ww, @StartDate, @EndDate)     -- а вот datediff даёт разницу 0

SET DATEFIRST @OldDatefirst -- возвращаем первый день недели, что был по-умолчанию

Оказалось, правда, что это не ошибка, а документированная особенность поведения функции datediff. Если бы я читал внимательно нужную литературу, то и сам бы знал, что

DATEDIFF() ignores the DATEFIRST setting, it assumes every week boundary begins on Sunday.  You can see this by putting your start and end dates on a Saturday and Monday, and cycling through all possible DATEFIRST values.  You'll see a "1" value regardless of setting.

SET DATEFIRST does control  the DOW return value on DATEPART though obviously.

А так как внимательно читал документацию Иван Клёва, то он мне эту цитату и предоставил…

Америку я открыл, а вот отчёт так и не построил. Как же быть?

К счастью, хотя бы datepart подвержен влиянию команды set datefirst. Оставалась надежда как-то извернуться и заставить сервер служить высоким финансовым целям. Вариант с вычитанием двух datepart в режиме недель пришлось отбросить, ведь функция возвращает номер недели в году. Стоит одной из дат оказаться в другом году - и метод тут же перестанет работать без значительного усложнения...

«Привычность мыслей надо гнать, столовый нож оружьем может стать»

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

declare @OldDatefirst int
declare @NewDatefirst int

declare @StartDate datetime
declare @EndDate datetime

declare @StartDateModified datetime
declare @EndDateModified datetime

SET @OldDatefirst = @@Datefirst -- запоминаем день недели
SET @NewDatefirst = datepart(dw, getdate()) -- неделя начинается сегодня

SET @StartDate = getdate() - 1 --вчера
SET @EndDate = getdate() + 1 -- завтра

SET DATEFIRST @NewDatefirst -- неделя начинается сегодня

--приводим обе даты к первому дню недели
SET @StartDateModified = @StartDate - datepart(dw, @StartDate) + 1
SET @EndDateModified = @EndDate - datepart(dw, @EndDate) + 1

SELECT
@StartDate, @EndDate,      -- сами даты
datediff(ww, @StartDate, @EndDate), -- разница в неделях для начальных дат
datediff(ww, @StartDateModified, @EndDateModified) -- теперь для datediff это дни разных недель

SET DATEFIRST @OldDatefirst -- возвращаем первый день недели, что был по-умолчанию

Всякий, кто сталкивался с такой задачей, проходил тот же путь поисков удачного решения. Может быть, предлагаемый способ послужит готовым рецептом, хотя наверняка есть и другие, более изящные методы доказать серверу, что понедельник начинается в субботу...

Нравится

Поделиться

2 комментария

Анатолий, а дело точно не в календаре? Стоит попробовать в календаре первым днем недели установить субботу.

"Alimova Anna" написал:

Здравствуйте, Анна!
Если верить Майкрософту, то дело точно не в календаре. Ибо сказано в MSDN:

...DATEDIFF() ignores the DATEFIRST setting...

Но, может, я неверно понял Ваш вопрос?

Показать все комментарии

Те, кто часто сталкиваются с задачей поднятия различных бекапов баз, рано или поздно задумываются об автоматизации этого процесса. Вот и я решил создать подобный скрипт.

Скрипт принимает один единственный параметр - полное имя файла бекапа.
Перед использованием необходимо отредактировать скрипт и настроить параметры под себя:

  • DestinationPath - путь, где будет развернут заданный бекап
  • ServerName, UserName, Password - параметры соединения с СУБД

После выполнения скрипта, в папке DestinationPath будет создана папка с именем базы, в которой будут храниться файлы данных и лога и произойдет связывание пользователя Supervisor с одноименным логином.

Нравится

Поделиться

12 комментариев

Сергей, я так понимаю Restore.sql отсутствует потому как в cmd написано

 del Restore.sql

:)

Именно - это временный файл. После его выполнения в нем уже нет надобности.

Теперь понял. А почему сразу не выполнить этот запрос через osql?

Сначала так и пробовал. Текст запроса оказался слишком большим для такого способа выполнения.

Здорово :) Я мечтала о такой штуке, а теперь она есть! :)

Уххх. Давно было пора.

Сереж, а можно сразу для восстановленной базы проводить сопоставление fkeys? Куда это добавить в скрипт?

Можно продублировать предпоследнюю строку и в ней заменить

exec sp_change_users_login 'Auto_Fix', 'Supervisor'

на

exec sp_change_users_login 'update_one', 'fkeys', 'fkeys'

Я бы добавил еще несколько улучшений:
1. Передача параметров через командную строку. Постоянно править файл неудобно. И если не передали, то спросить пользователя в консоли.
2. Добавить поддержку Windows авторизации
3. Обработать корректно если система не знает где утилита osql - в переменной PATH не добавлен путь или вы запустили файл не в папке с osql.

Для меня эти все опции, которые правятся в скрипте, статичны. И было бы не удобно их все время вводить в параметрах скрипта. Как варинат можно анализировать, если соотв-ий параметр не задан, то использвать константное значение.

Сергей, все верное - часть параметров очень часто статична: имя пользователя, пароль, сервер, а вот Имя БД, имя файла бекапа в моем случае нет, то править все время файл не удобно или хранить много таких скриптов или сделать самые часто изменяемые параметры - опциональными.

Добавил такую возможность.
Теперь, если задан второй параметр, то он воспринимается как имя базы, иначе имя базы определяется по имени файла бекапа.
Если задан третий параметр, то он воспринимается как папка, в которую будут помещены файлы БД, иначе - этот параметр берется из переменной DestinationPath, указанной в скрипте.

Эти два параметра не обязательные. Архив со скриптом обновил.

Показать все комментарии

Добрый день. Подскажите есть ли online репликация двух БД или это реализуется средствами MSSQL Server?

Нравится

12 комментариев

Здравствуйте. На текущий момент утилита online-репликации не поддерживается (разрабатывалась для версии 2.8). Была реализована через linked-сервера. Можете попробовать настроить средствами самого sql-сервера.
Terrasoft Support Team.

Спасибо за ответ!

Иван, а что значит онлайн репликация для вас?

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

"Мещеринов Иван Александрович" написал:через некоторое время она попадает в другую

Иван, а какое отличие от offline репликации?

Во времени синхронизации. Возможно просто я не точно выразился по этому поводу. Как я понял offline репликацию нужно запускать через какое то время и следить что она прошла правильно.
Суть задачи заключается в следующем:
Есть два терминальных сервера которые соединены по широкому каналу связи. Установить на оба сервера MSSQL Server чтобы каждый соединялся локально к своей БД но при этом они были зеркальные без вмешательства администратора.

Иван, если вы уверены в канале (скорость, надежность), то тогда да - стандартная transaction репликация вам подойдет, если канал не надежен, то следить за тем как работает репликация вам все равно придется.

Спасибо!) А не подскажите толковую статью по настроке?

Я когда-то настраивал на 2000 сервере, все было очень просто. У них удобный мастер, уверен что в 2008 еще проще :) Думаю лучше чем BOL вам никто не расскажет.

Иван, у mssql есть еще зеркалирование. Возможно вам подойдет.

Александр а вот как я понял при зеркальном отображении получается только одна база работает как основная а вторая просто копия первой? то есть нельзя организовать двустороннюю синхронизацию?

Про зеркалированию не подскажу. Если вам необходимо в две стороны синхронизировать данные, то тогда только репликация.

Показать все комментарии

Здравствуй, человек-читатель блогов на Community.

Хочу рассказать о своем недавнем опыте оптимизации курсора в SQL Server.
Первое, что нужно знать, курсор – это не хорошо, а плохо. Где есть возможность заменить курсор на INSERT SELECT или на использование временной таблицы, там нужно это сделать (за редким исключением). Курсор – это почти всегда дополнительные ресурсы сервера и резкое падение производительности по сравнению с другими решениями.
Второе, иногда без курсора не обойтись – там где не обойтись без построчного прохода по результату выборки. Вот в таких случаях очень важно правильно создать нужный тип курсора – тот, который соответствует решаемой задаче. Общий синтаксис объявления курсора имеет вид:

DECLARE cursor_name CURSOR
[ LOCAL | GLOBAL ]
[ FORWARD_ONLY | SCROLL ]
[ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ]
[ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ]
[ TYPE_WARNING ]
FOR select_statement
[ FOR UPDATE [ OF column_name [ ,...n ] ] ] [;]

Остановлюсь на первых трех строчках ключевых параметров.
    LOCAL или GLOBAL:    если хотим, чтобы курсор был доступен другим процедурам, функциям, пакетам в рамках нашей сессии, то GLOBAL – в этом случае за удалением курсора следим сами (команда DEALLOCATE). Во всех остальных случаях (т.е. в подавляющем своем большинстве) – LOCAL. Внимание, по умолчанию создается именно GLOBAL курсор!
    FORWARD_ONLY или SCROLL: если хотим ходить по курсору, как ненормальные, туда-сюда, то SCROLL, иначе – FORWARD_ONLY. Внимание, по умолчанию создается SCROLL курсор!
    STATIC или KEYSET, DYNAMIC, FAST_FORWARD: если хотим, чтобы при проходе по курсору отображалась актуальная информация из таблицы (т.е., если после открытия курсора, мы поменяли информацию в одном из полей таблицы и хотим, чтобы при проходе по курсору в нужной строчке курсора была уже обновленная информация), то используем или KEYSET (если КАЖДАЯ таблица, участвующая в выборке, имеет уникальный индекс) или DYNAMIC (самый медленный тип). Если же нам нужен снимок результата выборки после открытия курсора – STATIC (самый быстрый тип – копия результата выборки копируется в базу tempdb и работаем уже с ней). FAST_FORWARD = KEYSET+FORWARD_ONLY+READ_ONLY – пацаны из инета пишут, что STATIC дольше открывается (т.к. создается копия в tempdb), но быстрее работает, а FAST_FORWARD – наоборот. Так что если количество записей велико (насколько большое показывает практика), то применяем STATIC, иначе – FAST_FORWARD. Внимание, по умолчанию создается DYNAMIC курсор.

Таким образом, для большого кол-ва записей в большинстве случаев мой выбор:
DECLARE cursor_name CURSOR LOCAL FORWARD_ONLY STATIC FOR
select_statemen
t

для небольшого кол-ва записей:
DECLARE cursor_name CURSOR LOCAL FAST_FORWARD FOR
select_statement

Теперь перейдем к практике (что собственно и подтолкнуло меня к писанине сего).
Испокон веков при объявлении курсора я применял конструкцию DECLARE ... CURSOR LOCAL FOR...
При разработке интеграции с одной очень нехорошей базой, в которой нет ни одного индекса и не одного ключа, я применил тот же подход при объявлении курсоров, что и всегда. Выборка одного курсора содержала 225 000 записей. В результате процесс импорта данных из такой базы занял 15 часов 14 минут !!! И хотя импорт и был первичный (т.е. одноразовый), но даже для нормального тестирования такого импорта потребовалось бы несколько суток! После замены вышеназванной конструкции при объявлении курсора на DECLARE .. CURSOR LOCAL FORWARD_ONLY STATIC FOR.. весь процесс импорта занял ... внимание ... 10 минут 5 секунд !!! Так что игра точно стоит свеч.
Хочу повториться, что идеальный вариант - это все же не использовать курсоры вообще - для СУБД MS SQL намного роднее реляционный, а не навигационный подход.

Нравится

Поделиться

10 комментариев

Артем, классно, сколько пишу на T-SQL почти никогда не применял данные типы курсоров - всегда считал что это медленно и все старался заменить на insert select и декартово произведение, но оказывается эти параметры сильно ускоряют! Интересно! Надо будет запомнить. Есть правда еще один способ который ускоряет. Как известно если явно не стартовать транзакцию, то все запросы выполняются в рамках неявных транзакций, что сильно "бьет" по производительности, так вот если завести счетчик и стартовать транзакцию и коммитить после каждой 1000-й вставки, то скорость возрастает на порядки. Интересно попробовать это в случае с LOCAL FORWARD_ONLY STATIC :)
Только для надежности надо установить
SET CURSOR_CLOSE_ON_COMMIT OFF
А то курсор закроется после коммита...

Полезно, спасибо!

 

Артем, спасибо! Очень полезная информация.

Олег Лабьяк,
разработчик,
3-я линия Службы поддержки Terrasoft.

"Репко Артём" написал:если хотим ходить по курсору, как ненормальные, туда-сюда, то SCROLL

Артем, объясни, пожалуйста, как это понимать?

"Раловец Ольга" написал:как это понимать?

Ольга, простите за вольный стиль. Я имел ввиду, что если курсор SCROLL, то по нему можно двигаться как вперед, так и назад, а если FORWARD_ONLY - только вперед.

Думаю, Артём имел в виду использование не только с начала и до конца FETCH NEXT, а и таких возможностей, как FETCH FIRST, FETCH LAST, FETCH PRIOR, FETCH ABSOLUTE n, FETCH RELATIVE n.

Если коротко, цитата из хелпа по SQL:

Cursor Classifications
A cursor can be classified by the fetch options it supports:

Forward-only
Rows must be fetched serially from the first row to the last row. FETCH NEXT is the only fetch operation allowed.

Scrollable
Rows can be randomly fetched from anywhere in the cursor. All the fetch operations are allowed (except that dynamic cursors do not support fetch absolute).

Scrollable cursors are especially useful for supporting online applications. A cursor can be mapped to a grid or list box in the application. As the user scrolls up and down and all around the grid, the application uses scroll fetches to retrieve the rows from the cursor the user wants to see.

Олег Лабьяк,
разработчик,
3-я линия Службы поддержки Terrasoft.

Спасибо, Артем, спасибо, Олег. :)

"Репко Артём" написал:Ольга, простите за вольный стиль

Ваш стиль кажется мне приемлемым.

Коллеги, если всем понравилось, голосуем! +1
Артёму большое спасибо!

:twisted:

Огромнейшее спасибо! На "раскидывание" импортированных данных по таблицам (на 30 000 записей) вместо почти 5 часов ушло 50 минут.

Показать все комментарии