Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Microsoft SQL Server Новый топик    Ответить
Топик располагается на нескольких страницах: [1] 2   вперед  Ctrl      все
 Service broker для логирования, глюки  [new]
Кифирчик
Member

Откуда: СПб
Сообщений: 918
Недавно решил модернизировать существующую систему журналирования и ряд «автоматизаций» с использованием ServiceBroker. Вроде как асинхронность журналирование не будет тормозить, в при критической нагрузке — очередь постепенно в нужно порядке разгребется. Так как это вроде как «другой поток/подключение» не нужно париться с тем чтоб не потерять логи при rollback-ах.

Сделал контракты, очереди, активаторы, скрипты тестовые — все ОК. Все делает как нужно, ошибки тож хорошо ловит (пробовал на делении на ноль), ничего не ломается.

Но дальше начинается магия.

Запускаю пачкой юнит тесты (из C# вызываются хранимки с «новым» журналированием). Ошибок нет. Логи — пустые. Очередь — пустая.
Запускаю отдельно тест через отладчик — все норм, данные в логах есть. Запускаю ХП из SMS – логи есть.

Отлаживаю дебаггером в SMS в которой есть ошибка (обращение к несуществующему объекту). Пошагово иду, и там где логи должны записываться — таблица пустая, ни до ошибки, ни самой ошибки, ни после. Хранимки логов вызваются, в очередь сообщения отправляются, но активатор не вызывается.
Запускаю без дебаггера с «ошибкой» - логов тоже нет.
Запускаю приложение .NET, жму кнопочки, вызываются другие ХП с таким же логированием, логи есть.
Тут же еще раз дебаггером в SMS – логов нет.
Правлю хранимку, чтоб не было ошибки, запускаю, без отладки — логи есть.


Продолбался с этим конец рабочего дня. Сейчас сижу и ломаю голову. Это у меня что-то глючит или или в режиме дебаггера какой-то другой режим работы брокера? Или если ошибка посерьезнее чем деление на ноль, то запись в очередь умирает и это как-то просчитывается? И у брокера активатор работает не совсем в "отдельном потоке" и "отдельном подключении к БД"?
Или брокера для логов использовать не камельфо? Есть какие-то нюансы которые я не учел?
Куда пропадают логи когда пачкой работают юнит тесты?

сервер - 2014 Standart
12 дек 18, 20:47    [21762124]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Кифирчик
Member

Откуда: СПб
Сообщений: 918
по этой статейке с брокером работаю
https://germangorelkin.blogspot.com/2017/07/ms-sql-server.html
12 дек 18, 21:02    [21762136]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
invm
Member

Откуда: Москва
Сообщений: 9349
Кифирчик
Так как это вроде как «другой поток/подключение» не нужно париться с тем чтоб не потерять логи при rollback-ах.
Вот с этим как раз и нужно "париться", т.к. при откате транзакции все, что отсылалось в очередь также откатывается.

Обходится через event notifications - 17329722
12 дек 18, 21:07    [21762143]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Кифирчик
Member

Откуда: СПб
Сообщений: 918
invm,
я правильно понимаю, что "на пальцах", это примерно как я сделал, за исключением что попадание сообщения в очередь не через хранимку с dialog conversation а с использованием sp_trace_generateevent ?
12 дек 18, 22:03    [21762182]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
invm
Member

Откуда: Москва
Сообщений: 9349
Кифирчик
я правильно понимаю, что "на пальцах", это примерно как я сделал, за исключением что попадание сообщения в очередь не через хранимку с dialog conversation а с использованием sp_trace_generateevent ?
Т.е. разница в поведении при откате транзакции для вас несущественна?
13 дек 18, 09:26    [21762428]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Кифирчик
Member

Откуда: СПб
Сообщений: 918
invm, конечно существенна
я подразумевал реализацию а не поведение.
и судя по всему тип сообщения службы только [http://schemas.microsoft.com/SQL/Notifications/PostEventNotification] ?
13 дек 18, 09:41    [21762447]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
invm
Member

Откуда: Москва
Сообщений: 9349
Кифирчик
я подразумевал реализацию а не поведение.
Реализация да, через SB.
13 дек 18, 10:07    [21762495]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Кифирчик
Member

Откуда: СПб
Сообщений: 918
invm,
слепил из вашей заготовки и своего кода. на вскидку, ничего не упустил?
+
-- Включаем Service Broker
-- alter database db2_log set enable_broker with rollback immediate; 

use DB2_LOG
go

if exists (select 1 from sysobjects where id = object_id('SendLogMessage'         ) and type in ('P','PC')) drop procedure SendLogMessage
if exists (select 1 from sysobjects where id = object_id('AsyncLogQueueActivation') and type in ('P','PC')) drop procedure AsyncLogQueueActivation
if exists (select 1 from sysobjects where id = object_id('XmlMessageToTable'      ) and type in ('P','PC')) drop procedure XmlMessageToTable
if exists (select 1 from sysobjects where id = object_id('SimpleAuditData'        ) and type in ('U'     )) drop table SimpleAuditData
if exists (select 1 from sys.services                  where name = 'AsyncLogService'    ) drop service AsyncLogService
if exists (select 1 from sys.service_queues            where name = 'AsyncLogQueue'      ) drop queue AsyncLogQueue;
if exists(select * from sys.server_event_notifications where name = 'asyncLogEventNotify') drop event notification asyncLogEventNotify on server
-------------------------------------------------
go

-- Создаем очередь и сервисы

if object_id('AsyncLogQueueActivation', 'P') is null
 exec('create procedure AsyncLogQueueActivation as begin set nocount on; end;');
go

create queue AsyncLogQueue with
 status = on,
 retention = off,
 activation (status = on, procedure_name = AsyncLogQueueActivation, max_queue_readers = 5, execute as owner),
 poison_message_handling (status = on);
go

create service AsyncLogService authorization dbo  
on queue AsyncLogQueue (
    [http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]
);

go
create table SimpleAuditData
(
 ad_id         int identity,
 ad_OccurredAt datetime not null,
 ad_Login       sysname,
 ad_Application nvarchar(128),
 ad_Host        nvarchar(128),
 ad_ShortText   nvarchar(128),
 ad_LongText    nvarchar(4000),
 Stamp          timestamp,
 constraint PK_SimpleAuditData primary key (ad_id)
);
go

-- ХП которая разбирает XML и пишет его в таблицу
create procedure XmlMessageToTable @message xml
as
begin
    with s as
    (
            select
              n.value('EventType[1]'       , 'sysname'        ) as EventType
            , n.value('PostTime[1]'        , 'datetime'       ) as PostTime
            , n.value('TextData[1]'        , 'sysname'        ) as TextData
            , n.value('BinaryData[1]'      , 'varbinary(8000)') as BinaryData
            , n.value('DatabaseID[1]'      , 'int'            ) as DatabaseID
            , n.value('DatabaseName[1]'    , 'sysname'        ) as DatabaseName
            , n.value('ApplicationName[1]' , 'sysname'        ) as ApplicationName
            , n.value('HostName[1]'        , 'sysname'        ) as HostName
            , n.value('SessionLoginName[1]', 'sysname'        ) as SessionLoginName
            from
            @message.nodes('/EVENT_INSTANCE') x(n)
    )
    insert into SimpleAuditData
    (ad_OccurredAt, ad_Login, ad_Application, ad_Host, ad_ShortText, ad_LongText)
    select
        s.PostTime, s.SessionLoginName, s.ApplicationName, s.HostName, s.TextData, cast(s.BinaryData as nvarchar(4000))
    from s 
    --where DatabaseID = db_id();
end

-- Процедура которая будет принимать сообщения и обрабатывать их
go
alter procedure AsyncLogQueueActivation with execute as owner
as
begin
    set nocount on;

    declare @handle uniqueidentifier;
    declare @message xml;
    declare @message_type sysname;
 
    while (1=1)
    begin
        begin transaction;
 
            waitfor
            (
              receive top (1)
                @handle = conversation_handle,
                @message =  CAST(message_body AS XML),
                @message_type = message_type_name
              from AsyncLogQueue
            ), timeout 500;
 
            if (@@rowcount = 0)
            begin
              rollback transaction;
              print 'Error - нет данных в очереди'
              break;
            end
            if @message_type = N'http://schemas.microsoft.com/SQL/Notifications/EventNotification'
            begin
                exec XmlMessageToTable @message
            end
            else if @message_type = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
            begin
                end conversation @handle with cleanup;
            end
            else if @message_type = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
            begin
                --TODO
                --log errors
                end conversation @handle with cleanup;
            end 
        commit transaction;
    end
end
go

-- привязываем пользовательское событие к службе
create event notification asyncLogEventNotify
    on server
    for USERCONFIGURABLE_0 -- event_id = 82
    to service 'AsyncLogService', 'current database';
go

-- Процедура общего вида, с помощью которой можно отправлять сообщения
go
create procedure SendLogMessage @logMsg nvarchar(128), @logData nvarchar(1024)
as
begin
    set nocount on;
    -- sp_trace_generateevent 
    --      [     @eventid  = ]  event_id   
    --      [ , [ @userinfo = ] 'user_info' ]  
    --      [ , [ @userdata = ]  user_data  ]  
    -- event_id - numbers from 82 through 91
    declare @b varbinary(8000) = cast(@logData as varbinary(8000));
    exec sp_trace_generateevent 82, @logMsg, @b;
end
go


exec  SendLogMessage 'message1', 'data1'
exec  SendLogMessage 'message2', 'data2'
exec  SendLogMessage 'message3', 'data3'
exec  SendLogMessage 'message4', 'data4'
waitfor delay '00:00:01'
select * from SimpleAuditData
13 дек 18, 10:25    [21762517]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
invm
Member

Откуда: Москва
Сообщений: 9349
Кифирчик
на вскидку, ничего не упустил?
Вроде нет.
13 дек 18, 10:38    [21762535]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Владислав Колосов
Member

Откуда:
Сообщений: 7764
Кифирчик,

тесты откатывают транзакции и вместе с ними сообщения брокера. Вам для журналирования надо использовать механизм event notification, который передаёт сообщения брокеру независимо от отката.
13 дек 18, 11:16    [21762615]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Кифирчик
Member

Откуда: СПб
Сообщений: 918
Всем спасибо за наводки. Заработало на "боевой" базе.
Правда еще пришлось танцевать с бабунами.
допиливание чтоб на базе это все завелось (может кому пригодится)
use db_log
go
exec sp_changedbowner 'sa'
go
alter database db_log set trustworthy on
go
alter database db_log set enable_broker with rollback immediate; 
go

use master;
go
grant alter trace to user1
go


признаюсь не до конца осознал последствия trustworthy и changedbowner, но это заработало )))

а внутри обработчика сообщений пришлось добавить строку
set QUERY_GOVERNOR_COST_LIMIT 0;
иначе брокер вылетал с ошибкой
автор
the query has been canceled because the estimated cost of this query (8570) exceeds the configured threshold of 6000

Видать обработка XML со своими данными запиханного в BinaryData оказалась сравнительно ресурсоемкой.
14 дек 18, 15:51    [21764170]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
felix_ff
Member

Откуда: Moscow
Сообщений: 1369
Кифирчик,

подзравляю, теперь ваш любой db_owner может стать сисадмином сервера.
14 дек 18, 16:12    [21764193]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
invm
Member

Откуда: Москва
Сообщений: 9349
Кифирчик
признаюсь не до конца осознал последствия trustworthy и changedbowner, но это заработало )))
А зачем из процедуры активации обращаться к внешним ресурсам?
14 дек 18, 16:17    [21764199]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
felix_ff
Member

Откуда: Moscow
Сообщений: 1369
Кифирчик,

кстати вам еще следуют учитывать что для уведомлений о событиях диалоги не закрываются. они висят до тех пор пока не будет удалено уведомление.
Если у вас эта система логирования предполагает работать с высокой нагрузкой, следует озаботится вопросом что вы будете делать с открытыми диалогами
14 дек 18, 17:08    [21764246]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Кифирчик
Member

Откуда: СПб
Сообщений: 918
felix_ff
Кифирчик,

подзравляю, теперь ваш любой db_owner может стать сисадмином сервера.


invm
>признаюсь не до конца осознал последствия trustworthy и changedbowner, но это заработало )))

А зачем из процедуры активации обращаться к внешним ресурсам?


уточните плиз, вопрос относительно trustworthy или changedbowner?
14 дек 18, 17:09    [21764247]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
felix_ff
Member

Откуда: Moscow
Сообщений: 1369
Кифирчик,

совокупность.

сделав овнером базы пользователя из группы сисадмин вы расширили его права, любой пользователь группы db_owner может сделать
execute as user = 'dbo' тем самым переключив себя на контекст 'sa' но он пока еще закрыт в базе.

поставив trustworthy вы открыли песочницу, тем самым пользователь теперь не заперт в базе а получает доступ прав уровня sysadmin
14 дек 18, 17:12    [21764249]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Кифирчик
Member

Откуда: СПб
Сообщений: 918
felix_ff
Кифирчик,

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


это не спасает?
        waitfor
        (
          receive top (1)
              @handle     = conversation_handle
            , @msgBody    = cast( message_body as xml)
            --, @msgBodyStr = cast( message_body as nvarchar(max))
            , @msgType   = message_type_name
          from AsyncLogQueue
        ), timeout 500;

        ....

        else if @msgType = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
        begin
            end conversation @handle with cleanup;
        end
        else if @msgType = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
        begin
            --TODO
            --log errors
            end conversation @handle with cleanup;
        end 
14 дек 18, 17:14    [21764250]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
felix_ff
Member

Откуда: Moscow
Сообщений: 1369
Кифирчик,

не спасет.

срабатываение события посылает в очередь только тип сообщения [http://schemas.microsoft.com/SQL/Notifications/EventNotification]

никакого enddialog ему не приходит, поэтому у вас нижние ветки if никогда не сработают.

[http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog] поступит в очередь только при удалении уведомления инструкцие drop event notification

здесь вам по сути надо организовать логику вычитали сообщение закрыли диалог со стороны цели.

   if @message_type = N'http://schemas.microsoft.com/SQL/Notifications/EventNotification'
            begin
                exec XmlMessageToTable @message
                end conversation @handle;
            end
14 дек 18, 17:28    [21764263]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Владислав Колосов
Member

Откуда:
Сообщений: 7764
Кифирчик,

диалог надо закрывать с двух сторон - и на сервисе А и на сервисе Б. Сервис А в данном случае контролирует система Event Notification, а сервис Б - ваша процедура. Вы получаете и обрабатываете сообщение и закрываете со своей сторону диалог в любом случае, независимо от типа полученного сообщения.
14 дек 18, 17:40    [21764282]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Кифирчик
Member

Откуда: СПб
Сообщений: 918
felix_ff,
if @message_type = N'http://schemas.microsoft.com/SQL/Notifications/EventNotification'
            begin
                exec XmlMessageToTable @message
                end conversation @handle;
            end

в таком варианте, очередь обрабатывает только одно первое сообщение, все остальные sp_trace_generateevent я не вижу (((


Владислав Колосов
диалог надо закрывать с двух сторон - и на сервисе А и на сервисе Б. Сервис А в данном случае контролирует система Event Notification, а сервис Б - ваша процедура. Вы получаете и обрабатываете сообщение и закрываете со своей сторону диалог в любом случае, независимо от типа полученного сообщения.

Я не совсем понимаю "закрыть со своей стороны"

Со своей стороны я вызываю
  declare @a nvarchar(128) = 'AsyncLogMsg'
   declare @b varbinary(8000) = cast(@messageXml as varbinary(8000));
   exec sp_trace_generateevent 82, @a, @b;

как закрыть диалог с "моей стороны"?
или "моя сторона" подразумевается процедура активации очереди?
14 дек 18, 17:53    [21764307]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
invm
Member

Откуда: Москва
Сообщений: 9349
Кифирчик
уточните плиз, вопрос относительно trustworthy или changedbowner?
Да.
14 дек 18, 17:58    [21764312]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
felix_ff
Member

Откуда: Moscow
Сообщений: 1369
Кифирчик,

ну напишите так чтобы у вас при активации очереди цикл читал сообщения из очереди, если вдруг в цикле меняется conversation_handle или инструкция receive не возвращет сообщения, закрывается последний прочитанный диалог.

declare @h uniqueidentifier, @h2 uniqueidentifier
while 1=1 begin
     waitfor (receive top (1) @h = conversation_handle, @m = message_body from [quque]), timeout 500

     if @@rowcount = 0 begin 
          if @h is not null end conversation @h;
          break;
     end

     --логика обработки сообщения
     
     if @h2 <> @h and @h2 is not null end conversation @h2;

     set @h2 = @h;
end


а лучше вычитывать в буффер
declare @buffer table (h uniqueidentifier, message varbinary(max));
declare @tc int, @limit int, @m varbinary(max), @sql nvarchar(max);
while 1 = 1 begin
      waitfor (receive conversation_handle, message_body into @buffer from [queue]), timeout 500
      set @tc = @@rowcount;
      set @limit += @tc;
      if @tc = 0 or @limit > 100 break;
end;

declare cur cursor local fast_forward for select message from @buffer;
open cur;
while 1=1 begin
      fetch next from cur into @m;
      if @@fetch_status <> 0 break;
      exec <someproc> @m;
end;
close cur;
deallocate cur;

set @sql = N'';
select @sql = @sql + 'end conversation ' + quotename(cast(h as nchar(50)), nchar(39)) + nchar(59) + nchar(13) + nchar(10)
from @buffer;
exec (@sql);
14 дек 18, 18:55    [21764364]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
aleks222
Member

Откуда:
Сообщений: 983
Кифирчик
Недавно решил модернизировать существующую систему журналирования и ряд «автоматизаций» с использованием ServiceBroker. Вроде как асинхронность журналирование не будет тормозить, в при критической нагрузке — очередь постепенно в нужно порядке разгребется.


Интересно, в чем разница: писать в очередь или сразу писать в журнал?

ЗЫ. Битва с ветряными мельницами?
15 дек 18, 06:06    [21764618]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
Кифирчик
Member

Откуда: СПб
Сообщений: 918
aleks222
Кифирчик
Недавно решил модернизировать существующую систему журналирования и ряд «автоматизаций» с использованием ServiceBroker. Вроде как асинхронность журналирование не будет тормозить, в при критической нагрузке — очередь постепенно в нужно порядке разгребется.


Интересно, в чем разница: писать в очередь или сразу писать в журнал?

ЗЫ. Битва с ветряными мельницами?

Хороший вопрос, заставил задуматься...
Но он был бы справедлив, если бы запись в очередь равнялась записи в таблицу.

У нас простая схема (запись в таблицу) работает уже лет пять, и как показывает практика, в этих записях хрен разберешься. одновременно работают хранимки вызванные пользователем, джобы, триггеры (через которые мы выполняем автоматизацию с соседней системой).
Куча проблем с ответом на вопросы кто кого вызвал, откуда, к какому именно вызову относится строка, к какому бизнес объекту. Логи на хранимки/модули нужно как-то включать/выключать по мере необходимости.
В случае ошибки - откатываются и логи, и следов ошибки вообще нет. Ну и триггеры соседней системы также откатываются.
В итоге, в виду того что часть работы это аутосорс у заказчика, он временами звонит, и говорит "вот, у меня тут вчера так то и так то глюкнуло, разберитесь", и начинается бурный секс с этим потоком данных. И нужно определенное "наитие" и бутылка водки чтоб в этом разобраться и возможно подправить последствия. В целом толку от этого лога очень мало.
Это "простая" система. И даже на ней, коллеги следящие за перфомансом, комментят записи в логи получают профит.

Недавно придумали новую систему, которая должна решить проблемы перечисленные выше:
- дополнительно пишется ID "бизнес процесса", внутри которого может быть несколько ХП у которых тоже пишется свой не похожий на соседние ID
- есть поле для "бизнес объекта"
- есть система включения/выключение логирования (через табличку)
- упрощена запись (длинная, с указанием кучи аргументов только первая строка)
- не теряются логи после отката транзакции (через промежуточное хранение в табличной переменной)
То есть логгирование хранимки в таком варианте - это минимум две записи о начале/окончании, и при каждой записи обращения к таблице настроек и мета информации плюс запись в логи.
Это определенно окажет еще большее влияние на производительность.
Также, попадаются ошибки из-за которых все равно логи откатываются, вариант с табличной переменной работает не идеально.

Вчера на ноуте запустил тест, в котором ОЧЕНЬ много логов пишется через обсуждаемый вариант логирования (через брокера и event-ы) (упрощенный, только вставка без использование мета информации). Тесты за минуту забили в очередь 100к записей, которые асинхронно минут 15..20 распихивались в таблицу логов. По мне так это пример что это минимально нагружает хранимые процедуры.

Также, мне кажется крайне важным асинхронно развязаться с триггерами соседней системы, не камельфо в них выполнять свои хранимые процедуры. И судя по всему тут доктор прописал использовать ServiceBroker.
Сейчас появилась возможность, и я начал копать в сторону ServiceBroker-a, и решил начать с логов.

Что касается ветряных мельниц - с точки зрения того что есть куча других задач, приходится делать фоново, да и без этого и так работает, то это определенно на ветряную мельницу смахивает.
С точки зрения "хочется чтоб было не через ж#пу" и меньше долбаться с анализом проблем - это не мельница а крепость на пути к светлому будущему )
15 дек 18, 12:38    [21764736]     Ответить | Цитировать Сообщить модератору
 Re: Service broker для логирования, глюки  [new]
aleks222
Member

Откуда:
Сообщений: 983
Кифирчик
По мне так это пример что это минимально нагружает хранимые процедуры.


Тебе уже сказали: все очереди сервис брокера - суть таблицы.

Т.е. твой пример пишет два раза.
И это, канешно, быстрее, чем один раз.
15 дек 18, 14:42    [21764812]     Ответить | Цитировать Сообщить модератору
Топик располагается на нескольких страницах: [1] 2   вперед  Ctrl      все
Все форумы / Microsoft SQL Server Ответить