あなたが言うとき、「トリガーを使用せずに」、あなたが意味するかいずれかのテーブル上のトリガまたは単に行ごとのトリガーを?
あなたはので、私は尋ねることを賢明に利用して欲しいものを得ることができるCONTEXT_INFO()
機能がありますが、それを保証する必要があるでしょうSET CONTEXT_INFO
、あなたの操作が行われる前に、正しく呼び出されました。
そのための1つの場所は、次のようにサーバーレベルのログオントリガー(つまり、データベース/オブジェクトレベルのトリガーではない)かもしれません。
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
。これにより、展開ロジックが1か所に保持されます。
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
自分を呼び出すために十分なアクセス権を持ち、1日を混乱させる(たとえば、偽の値、または特別に細工された保存された注射で)そのユーザーにとっては些細なことです。上手。
ホスト名に関しては、のClientHost
要素がEVENTDATA()
IPアドレス(または<local machine>
インジケータ)を与えると思います。あなたが技術的に戻って、ホスト名への逆DNSルックアップを行うためにCLRを使用することができますが、これらはすべてのために行うにはあまりにも遅くなる傾向がありINSERT
、私が推薦するので、ないそうします。
あなたがいる場合持っているホスト名を持っている、あなたは定期的にアウトオブバンドプロセスとして、そして、ローカルのDHCPサーバーやDNSゾーンファイルから現在のリースを別のテーブルを移入するためにSQLエージェントジョブを使用する場合があり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
ロックアウトされた場合、DACを使用して、ログイントリガーを削除または無効にすることができます。
C:\> sqlcmd -S localhost -d master -A
1> DISABLE TRIGGER tr_audit_login ON ALL SERVER
2> GO