Коли ви говорите "без використання тригерів", ви маєте на увазі будь-які тригери або просто тригерні тригери на таблицях?
Я прошу, тому що ви, можливо, зможете отримати те, що хочете, при розумному використанні CONTEXT_INFO()
функції, але вам потрібно буде переконатися, що SET CONTEXT_INFO
було викликано правильно, перш ніж відбуватимуться ваші операції.
Одне місце для цього може бути тригером входу на рівні сервера (тобто не тригером рівня бази даних / об'єктів), наприклад:
USE master
GO
CREATE TRIGGER tr_audit_login
ON ALL SERVER
WITH EXECUTE AS 'sa'
AFTER LOGON
AS BEGIN
BEGIN TRY
DECLARE @eventdata XML = EVENTDATA();
IF @eventdata IS NOT NULL BEGIN
DECLARE @spid INT;
DECLARE @client_host VARCHAR(64);
SET @client_host = @eventdata.value('(/EVENT_INSTANCE/ClientHost)[1]', 'VARCHAR(64)');
SET @spid = @eventdata.value('(/EVENT_INSTANCE/SPID)[1]', 'INT');
-- pack the required data into the context data binary
-- (spid is just an example of packing multiple data items in a single field: you would probably use @@SPID at the point of use, instead)
DECLARE @context_data VARBINARY(128);
SET @context_data = CONVERT(VARBINARY(4), @spid)
+ CONVERT(VARBINARY(64), @client_host);
-- persist the spid and host into session-level memory
SET CONTEXT_INFO @context_data;
END
END TRY
BEGIN CATCH
/* do better error handling here...
* logon trigger can lock all users out of server, so i am just swallowing everything
*/
DECLARE @msg NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR('%s', 10, 1, @msg) WITH LOG;
END CATCH
END
Потім ви можете додати обмеження за замовчуванням до таблиці, щоб зберігати контекст (для швидкості вставки):
ALTER TABLE cdc.schema_table_CT
ADD ContextInfo varbinary(128) NULL DEFAULT(CONTEXT_INFO())
Коли ви це зробите, ви можете запитати цей ContextInfo
стовпець з невеликими фрагментами:
SELECT *
,spid = CONVERT(INT, SUBSTRING(ContextInfo, 1, 4))
,client = CONVERT(VARCHAR(64), SUBSTRING(ContextInfo, 5, 64))
FROM cdc.schema_table_CT
Технічно ви можете це робити SUBSTRING
і CONVERT
вкладати як частину обмеження за замовчуванням і просто зберігати там клієнтський IP, але, можливо, буде швидше зберігати весь контекст там (як це робиться на кожному INSERT
) і витягувати лише значення в SELECT
коли вони вам потрібні.
Я, можливо, схильний завершити всі свої SUBSTRING
і CONVERT
дзвінки в однорядну функцію вбудованої таблиці, яку я б робив, CROSS APPLY
коли це було необхідно. Це зберігає логіку розпакування в одному місці:
CREATE FUNCTION fn_context (
@context_info VARBINARY(128)
)
RETURNS TABLE
AS RETURN (
SELECT
spid = CONVERT(INT, SUBSTRING(@context_info, 1, 4))
,client = CONVERT(VARCHAR(64), SUBSTRING(@context_info, 5, 64))
)
GO
SELECT *
FROM cdc.schema_table_CT s
CROSS APPLY dbo.fn_context(s.ContextInfo) c
Зауважте, що CONTEXT_INFO
це лише 128-байт VARBINARY
. Якщо вам потрібно більше даних, ніж ви можете вмістити в 128 байт, я створив би таблицю для зберігання всіх цих даних, вставив би як рядок для цього "сеансу" в таблицю в тригері входу і встановив CONTEXT_INFO
сурогатне значення ключової таблиці цієї таблиці
Слід також зазначити, що, оскільки це лише обмеження за замовчуванням, тривіально для користувача, що має належні права, може перезаписати ці контекстні дані в таблицю спокою. Звичайно, те ж саме стосується і всіх інших стовпців у таблицях у стилі "аудит".
Було б добре, якби це міг бути стійкий обчислюваний стовпець, а не за замовчуванням, але CONTEXT_INFO()
функція недетермінована, тому вона не працює (ви, можливо, зможете використовувати деякі FUNCTION
хитрощі навколо VIEW
, але я б не ).
Це також банально для цього користувача, який має достатній доступ, щоб зателефонувати SET CONTEXT_INFO
собі та зіпсувати свій день (наприклад, з підробленими значеннями або спеціально складеним збереженим введенням), тому ставіться до вмісту з підозрою та обережністю, кодуйте його до показу та обробляйте винятки Ну.
Що стосується імені хоста, я думаю, що ClientHost
елемент EVENTDATA()
надає вам IP-адресу (або <local machine>
індикатор). Хоча ви технічно можете використовувати CLR для зворотного пошуку DNS назад до імені хоста, вони, як правило, занадто повільно робити для кожного INSERT
, тому я рекомендую цього не робити.
Якщо у вас є ім'я хоста, ви можете використовувати завдання SQL Agent, щоб періодично заповнювати окрему таблицю поточними орендами з вашого локального сервера DHCP або DNS-зони, як поза межами діапазону, і LEFT JOIN
для цього в майбутні запити (або загорнути в скаляр, FUNCTION
щоб надати значення обмеження за замовчуванням, на момент часу).
Знову ж таки, слід зазначити, що якщо у додатку є будь-який компонент із загальнодоступним компонентом, IP-адреси та імена хостів ненадійні (наприклад, через NAT). Навіть якщо він не є загальнодоступним, для більшості карт IP-назв / хостів визначений компонент, який може базуватися на часі, який може знадобитися для врахування.
Нарешті, перед тим, як застосувати тригер входу, можливо, варто ввімкнути спеціальне адміністраторне з'єднання вашого сервера. Якщо тригер входу будь-яким чином не працює, це може запобігти входу в систему всіх користувачів (включаючи облікові записи sysadmin):
USE master
GO
-- you may want to do this, so you have a back-out if the login trigger breaks login
EXEC sp_configure 'remote admin connections', 1
GO
RECONFIGURE
GO
Якщо ви заблоковані, ЦАП може бути використаний для скидання або відключення тригера входу:
C:\> sqlcmd -S localhost -d master -A
1> DISABLE TRIGGER tr_audit_login ON ALL SERVER
2> GO