เมื่อคุณพูดว่า "ไม่ใช้ทริกเกอร์" คุณหมายถึงทริกเกอร์ใด ๆหรือทริกเกอร์แบบทีละแถวในตารางหรือไม่?
ฉันถามเพราะคุณอาจได้รับสิ่งที่คุณต้องการด้วยการใช้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
คีย์ตัวแทนของตารางนั้น
คุณควรทราบด้วยว่าเนื่องจากเป็นเพียงข้อ จำกัด เริ่มต้นจึงเป็นเรื่องเล็กน้อยสำหรับผู้ใช้ที่มีสิทธิพิเศษที่จะเขียนทับข้อมูลบริบทนั้นในตารางที่เหลือ แน่นอนเช่นเดียวกันสำหรับคอลัมน์อื่น ๆ ทั้งหมดในตารางสไตล์ 'audit' เช่นกัน
มันจะดีถ้ามันอาจจะเป็นคอลัมน์ที่คำนวณไว้แทนที่จะเป็นค่าเริ่มต้น แต่CONTEXT_INFO()
ฟังก์ชั่นนั้นไม่ได้กำหนดไว้ดังนั้นมันจึงเป็นแบบไม่ไป (คุณอาจใช้FUNCTION
กลอุบายรอบ ๆVIEW
ได้ แต่ฉันจะไม่ )
นอกจากนี้ยังเป็นเรื่องเล็กน้อยสำหรับผู้ใช้ที่มีการเข้าถึงที่เพียงพอเพื่อเรียกSET CONTEXT_INFO
ตัวเองและทำให้ยุ่งเหยิงในวันของคุณ (เช่นค่าของปลอมหรือการฉีดที่จัดทำขึ้นเป็นพิเศษ) ดังนั้นรักษาเนื้อหาด้วยความสงสัยและการดูแลรักษาเข้ารหัสก่อนแสดงและจัดการข้อยกเว้น ดี.
สำหรับชื่อโฮสต์ฉันคิดว่าClientHost
องค์ประกอบของEVENTDATA()
ให้ที่อยู่ IP (หรือ<local machine>
ตัวบ่งชี้) ให้คุณ ในขณะที่คุณสามารถใช้ CLR ในการค้นหา reverse-DNS กลับไปที่ชื่อโฮสต์ได้ แต่สิ่งเหล่านี้มักจะช้าเกินไปสำหรับทุกคนINSERT
ดังนั้นฉันขอแนะนำไม่ให้ทำเช่นนั้น
หากคุณต้องมีชื่อโฮสต์คุณอาจต้องการใช้งานตัวแทน SQL เพื่อเติมข้อมูลตารางแยกต่างหากเป็นระยะโดยมีสัญญาเช่าปัจจุบันจากเซิร์ฟเวอร์ DHCP ท้องถิ่นหรือไฟล์โซน DNS ของคุณเป็นกระบวนการนอกวงและLEFT JOIN
ใน ข้อความค้นหาในอนาคต (หรือตัดคำในเซนต์คิตส์และเนวิสFUNCTION
เพื่อให้ค่ากับข้อ จำกัด เริ่มต้นสำหรับจุดในเวลา)
อีกครั้งคุณควรทราบว่าหากแอปพลิเคชันมีองค์ประกอบสาธารณะประเภทใดที่อยู่ IP และชื่อโฮสต์จะไม่น่าเชื่อถือ (เช่นเนื่องจาก NAT) แม้ว่าจะไม่ใช่แบบสาธารณะ แต่ก็มีองค์ประกอบตามเวลาในการแม็พ IP / ชื่อโฮสต์ส่วนใหญ่ซึ่งคุณอาจจำเป็นต้องคำนึงถึง
สุดท้ายก่อนที่จะใช้ทริกเกอร์การเข้าสู่ระบบของคุณอาจเป็นการเปิดการเชื่อมต่อผู้ดูแลระบบของเซิร์ฟเวอร์ของคุณโดยเฉพาะ หากทริกเกอร์การเข้าสู่ระบบแตกในทางใด ๆ ก็สามารถป้องกันผู้ใช้ทุกคนเข้าสู่ระบบ (รวมถึงบัญชีดูแลระบบ):
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