在触发器中是否为SELECT * OK。还是我在找麻烦?


8

我在工作中陷入一场辩论,我需要一些有关我可能会忽略的陷阱的建议。

想象一下使用触发器将已删除记录复制到审核表的情况。触发器使用SELECT *。每个人都指向并大喊,并告诉我们这有多糟糕。

但是,如果对主表的结构进行了修改,而忽略了审计表,则触发器将产生一个错误,使人们知道审计表也需要修改。

在我们的DEV服务器上进行测试的过程中将捕获该错误。但是我们需要确保生产匹配DEV,因此我们在生产系统中允许SELECT *(仅适用于触发器)。

所以我的问题是:我被迫删除SELECT *,但是我不确定如何确保我们自动捕获这种性质的开发错误,任何想法或这是最佳实践?

我在下面整理了一个例子:

--Create Test Table
CREATE TABLE dbo.Test(ID INT IDENTITY(1,1), Person VARCHAR(255))
--Create Test Audit Table
CREATE TABLE dbo.TestAudit(AuditID INT IDENTITY(1,1),ID INT, Person VARCHAR(255))

--Create Trigger on Test
CREATE TRIGGER [dbo].[trTestDelete] ON [dbo].[Test] AFTER DELETE
NOT FOR REPLICATION
AS
BEGIN
    SET NOCOUNT ON;
    INSERT  dbo.TestAudit([ID], [Person])
    SELECT  *
    FROM    deleted
END

--Insert Test Data into Test
INSERT INTO dbo.Test VALUES
('Scooby')
,('Fred')
,('Shaggy')

--Perform a delete
DELETE dbo.Test WHERE Person = 'Scooby'

更新(重新表达问题):

我是一名DBA,并且需要通过贡献给我们的最佳做法文档来确保开发人员不会提供经过深思熟虑的部署脚本。当开发人员忽略审核表(这是一个安全网)时,SELECT *会在DEV中导致错误,因此该错误会在开发过程的早期被发现。但是在SQL宪法第二修正案的某个地方,它显示为“您不得使用SELECT *”。因此,现在有人在努力摆脱安全网。

您将如何更换安全网,或者我认为这是扳机的最佳做法?

更新2 :(解决方案)

谢谢您的所有输入,我不确定我是否有明确的答案,因为这似乎是一个非常灰色的主题。但是,您集体提供了一些讨论点,可以帮助我们的开发人员在定义自己的最佳实践时继续前进。

感谢Daevin您的贡献,您的答案为我们的开发人员可以实施的某些测试机制提供了基础。+1

谢谢CM_Dayton,您的建议有助于最佳实践,这对任何开发审计触发器的人都将是有益的。+1

非常感谢您ypercube,您对有关表经历不同形式的定义更改的问题提出了很多思考。+1

结论:

Is Select * ok in a tigger? 是的,这是灰色区域,请不要盲目遵循“选择*是错误的”意识形态。

Am I asking for Trouble? 是的,我们所做的不只是将新列添加到表中。


您在问题中回答自己。如果更改源表,则select *将中断。为了确保dev和prod相同,请使用某种形式的源代码控制。
Bob Klimes '02

稍微宽一点的问题,您多久删除一次记录以及占表总数的多少?触发器的替代方法是具有一个将行标记为已删除的位标志,以及按计划运行以将其移至日志表的代理作业。您可以将其内置到代理作业检查中,以查看表模式是否匹配,并且如果该步骤有问题,则该作业将简单地失败,直到将其修复。
Tanner

我通常都同意这种SELECT *懒惰,但是由于您有正当的理由使用它,因此它比黑白更灰色。您应该尝试执行的操作是这样的,但是将其调整为不仅具有相同的列数,而且列名称和数据类型相同(因为有人可以更改数据类型,并且仍然会导致通常无法捕获的数据库问题)和您的SELECT *“安全网”
戴文(Daevin)'17年

3
我喜欢将其SELECT *用作安全网的想法,但并不能解决所有情况。例如,如果您删除一列并再次添加它。这将更改列的顺序,并且(除非所有列均为同一类型),由于隐式类型转换,插入到审计表中将失败或导致数据丢失。
ypercubeᵀᴹ

2
我还想知道,当从表中删除列时,审计设计将如何进行。您还会从审核表中删除该列(并丢失所有以前的审核数据)吗?
ypercubeᵀᴹ

Answers:


10

通常,它被认为是“惰性”编程。

假设你专门将两个值到你的TestAudit表在这里,我会小心,以确保您的选择也越来越正好两个值。因为如果由于某种原因该Test表具有或曾经获得第三列,则此触发器将失败。

与您的问题没有直接关系,但是如果您要设置审核表,我还将在您的TestAudit表中添加一些其他列以...

  • 跟踪您要审核的操作(在这种情况下是删除,还是插入或更新)
  • 日期/时间列,以跟踪审核事件发生的时间
  • 用户ID列,以跟踪谁执行了您要审核的操作。

这样就导致查询如下:

INSERT dbo.TestAudit([ID], [Person], [AuditAction], [ChangedOn], [ChangedBy])
SELECT [ID], [Person], 
   'Delete', -- or a 'D' or a numeric lookup to an audit actions table...
   GetDate(), -- or SYSDATETIME() for greater precision
   SYSTEM_USER -- or some other value for WHO made the deletion
FROM deleted

这样,您将获得所需的确切列,并且正在审核审核事件的内容/时间/原因/原因。


“用户ID”这是审核中的棘手问题。通常,数据库帐户与实际用户不对应。通常,它们对应于单个Web应用程序或其他类型的组件,并具有该组件使用的一组凭据。(有时,这些组件也将共享凭据。)因此,数据库凭据作为谁执行了什么操作的标识符非常有用,除非您只是对执行此操作的组件感兴趣。但是据我所知,使用触发器函数传递识别“谁”的应用程序数据并不容易。
jpmc26

请参阅更新。
pacreely

通常,SELECT *可能会带来的另一个问题(尽管在您的示例中可能不是),如果基础表的列与插入列的顺序不同,则插入将失败。
CaM

3

我对您的问题发表了评论,但我认为我会尝试提出一个代码解决方案。

我通常同意SELECT *懒惰,但是由于您有正当的理由使用它,因此它比黑白更灰色。

应该尝试执行的操作(在我看来)是这样的,但请对其进行调整以确保列名和数据类型相同(因为有人可以更改数据类型,并且仍然会导致db中的问题通常不会被您的SELECT *“安全性”所困扰。净'。

您甚至可以创建一个函数,该函数可以让您快速检查表的审核版本是否与非审核版本匹配:

-- The lengths are, I believe, max values for the corresponding db objects. If I'm wrong, someone please correct me
CREATE FUNCTION TableMappingComparer(
    @TableCatalog VARCHAR(85) = NULL,
    @TableSchema VARCHAR(32) = NULL,
    @TableName VARCHAR(128) = NULL) RETURNS BIT
AS
BEGIN
    DECLARE @ReturnValue BIT = NULL;
    DECLARE @VaryingColumns INT = NULL;

    IF (@TableCatalog IS NOT NULL
            AND @TableSchema IS NOT NULL
            AND @TableName IS NOT NULL)
        SELECT @VaryingColumns = COUNT(COLUMN_NAME)
            FROM (SELECT COLUMN_NAME,
                        DATA_TYPE -- Add more columns that you want to ensure are identical
                    FROM INFORMATION_SCHEMA.COLUMNS
                    WHERE TABLE_CATALOG = @TableCatalog
                        AND TABLE_SCHEMA = @TableSchema
                        AND TABLE_NAME = @TableName
                EXCEPT
                    SELECT COLUMN_NAME,
                            DATA_TYPE -- Add more columns that you want to ensure are identical
                        FROM INFORMATION_SCHEMA.COLUMNS
                        WHERE (TABLE_CATALOG = @TableCatalog
                            AND TABLE_SCHEMA = @TableSchema
                            AND TABLE_NAME = @TableName + 'Audit')
                            AND (COLUMN_NAME != 'exclude your audit table specific columns')) adt;
    IF @VaryingColumns = 0
        SET @ReturnValue = 1
    ELSE IF @VaryingColumns > 0
        SET @ReturnValue = 0

    RETURN @ReturnValue;
END;

SELECT ... EXCEPT SELECT ...Audit会告诉你在什么表列不是在审计表。您甚至可以更改函数以返回不相同的列名称,而不仅仅是返回它们是否映射,甚至引发异常。

然后,您可以从移动之前运行这个DEVPRODUCTION服务器在数据库的每个表,在使用光标:

SELECT TABLE_NAME
    FROM INFORMATION_SCHEMA.TABLES
    WHERE NOT (TABLE_NAME LIKE '%Audit')

1
看到问题的更新
'17年

很高兴我能帮上忙。感谢您阅读所有答案并将其带回您的团队寻求建议;适应能力和改进意愿是技术部门保持公司正常运转的方式!:D
戴文'17

0

将提示触发器的语句将失败,并且触发器将失败。最好记录下触发器和审计跟踪,以便您知道修改查询以添加列而不是指定*。

至少您应该修改触发器,以使其在将错误记录到表时可以正常地失败,并且可能在触发器将错误记录到的表上放置警报。

这也让我想到,当有人更改表并添加更多列或删除列时,您可以放置​​触发器或警报,以通知您追加触发器。

我认为在性能方面,*不会做任何改变,只会增加事情发生变化时失败的可能性,并且当您在需要时通过网络获取更多信息时也会导致网络延迟。*存在一个时间和地点,但是如上所述,我认为您有更好的解决方案和工具来尝试。


0

如果您的原始表或审计表结构完全改变,则可以保证选择的*会遇到问题。

INSERT INTO [AuditTable]
(Col1,Col2,Col3)
SELECT * 
FROM [OrigTable] or [deleted];

如果任何一个改变,触发器将出错。

您可以这样做:

INSERT INTO [AuditTable]
SELECT * 
FROM [OrigTable];

但是,正如CM_Dayton所说的那样,这是懒惰的编程,为其他不一致打开了大门。为了使这种情况起作用,您必须绝对确保同时更新两个表的结构。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.