【问题标题】:Accessing data with stored procedures使用存储过程访问数据
【发布时间】:2008-10-29 15:29:04
【问题描述】:

“最佳实践”之一是通过存储过程访问数据。我明白为什么这种情况很好。 我的动机是拆分数据库和应用程序逻辑(表可以更改,如果存储过程的行为相同),SQL注入防御(用户不能执行“select * from some_tables”,他们只能调用存储过程),以及安全性(在存储过程中可以是安全的“任何东西”,用户不能选择/插入/更新/删除数据,这不适合他们)。

我不知道如何使用动态过滤器访问数据。

我使用的是 MSSQL 2005。

如果我有桌子:

CREATE TABLE tblProduct (
   ProductID uniqueidentifier -- PK
   , IDProductType uniqueidentifier -- FK to another table
   , ProductName nvarchar(255) -- name of product
   , ProductCode nvarchar(50) -- code of product for quick search
   , Weight decimal(18,4)
   , Volume decimal(18,4)
)

那么我应该创建 4 个存储过程(创建/读取/更新/删除)。

“创建”的存储过程很简单。

CREATE PROC Insert_Product ( @ProductID uniqueidentifier, @IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
   INSERT INTO tblProduct ( ProductID, IDProductType, ... etc .. ) VALUES ( @ProductID, @IDProductType, ... etc ... )
END

“删除”的存储过程也很简单。

CREATE PROC Delete_Product ( @ProductID uniqueidentifier, @IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
    DELETE tblProduct WHERE ProductID = @ProductID AND IDProductType = @IDProductType AND ... etc ...
END

“更新”的存储过程与“删除”类似,但我不确定这是正确的方法,如何去做。我认为更新所有列效率不高。

CREATE PROC Update_Product( @ProductID uniqueidentifier, @Original_ProductID uniqueidentifier, @IDProductType uniqueidentifier, @Original_IDProductType uniqueidentifier, ... etc ... ) AS BEGIN
   UPDATE tblProduct SET ProductID = @ProductID, IDProductType = @IDProductType, ... etc ...
      WHERE ProductID = @Original_ProductID AND IDProductType = @Original_IDProductType AND ... etc ...
END

最后一个“读取”的存储过程对我来说有点神秘。如何通过复杂条件的过滤器值?我有几点建议:

使用XML参数传递where条件:

CREATE PROC Read_Product ( @WhereCondition XML ) AS BEGIN
    DECLARE @SELECT nvarchar(4000)
    SET @SELECT = 'SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume FROM tblProduct'

    DECLARE @WHERE nvarchar(4000)
    SET @WHERE = dbo.CreateSqlWherecondition( @WhereCondition ) --dbo.CreateSqlWherecondition is some function which returns text with WHERE condition from passed XML

    DECLARE @LEN_SELECT int
    SET @LEN_SELECT = LEN( @SELECT )
    DECLARE @LEN_WHERE int
    SET @LEN_WHERE = LEN( @WHERE )
    DECLARE @LEN_TOTAL int
    SET @LEN_TOTAL = @LEN_SELECT + @LEN_WHERE
    IF @LEN_TOTAL > 4000 BEGIN
        -- RAISE SOME CONCRETE ERROR, BECAUSE DYNAMIC SQL ACCEPTS MAX 4000 chars
    END

    DECLARE @SQL nvarchar(4000)
    SET @SQL = @SELECT + @WHERE

    EXEC sp_execsql @SQL
END

但是,我认为一个查询的“4000”个字符的限制是丑陋的。

下一个建议是对每一列使用过滤表。将过滤器值插入过滤器表,然后调用带有过滤器 ID 的存储过程:

CREATE TABLE tblFilter (
   PKID uniqueidentifier -- PK
   , IDFilter uniqueidentifier -- identification of filter
   , FilterType tinyint -- 0 = ignore, 1 = equals, 2 = not equals, 3 = greater than, etc ...
   , BitValue bit , TinyIntValue tinyint , SmallIntValue smallint, IntValue int
   , BigIntValue bigint, DecimalValue decimal(19,4), NVarCharValue nvarchar(4000)
   , GuidValue uniqueidentifier, etc ... )

CREATE TABLE Read_Product ( @Filter_ProductID uniqueidentifier, @Filter_IDProductType uniqueidentifier, @Filter_ProductName uniqueidentifier, ... etc ... ) AS BEGIN
   SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume
   FROM tblProduct
   WHERE ( @Filter_ProductID IS NULL
            OR ( ( ProductID IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_ProductID AND FilterType = 1 ) AND NOT ( ProductID IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_ProductID AND FilterType = 2 ) )
      AND ( @Filter_IDProductType IS NULL
            OR ( ( IDProductType IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_IDProductType AND FilterType = 1 ) AND NOT ( IDProductType IN ( SELECT GuidValue FROM tblFilter WHERE IDFilter = @Filter_IDProductType AND FilterType = 2 ) )
      AND ( @Filter_ProductName IS NULL OR ( ... etc ... ) ) 
END

但我认为这个建议有点复杂。

是否有一些“最佳实践”来执行这种类型的存储过程?

【问题讨论】:

    标签: sql sql-server-2005 stored-procedures crud


    【解决方案1】:

    对于读取数据,您不需要存储过程来确保安全或分离出逻辑,您可以使用视图。

    只授予视图上的选择。

    您可以限制显示的记录、更改字段名称、将许多表连接成一个逻辑“表”等。

    【讨论】:

    • 我应该使用视图还是函数?我认为这些功能要灵活得多。为什么不使用函数而不是视图是某种原因还是它不重要?
    • 通常 DBMS 优化器比函数更能理解视图。
    • 再一次,它可能不会 - 在大型生产数据库中,当我们将一些较大的复杂查询从视图切换到函数时,我们的性能得到了非常显着的提升......
    • 同意:测试和分析是最重要的。不过,就我个人而言,我认为所有函数都不应该依赖表中的数据——只是因为我喜欢函数式编程。
    • 单独使用视图并不能防止 SQL 注入。
    【解决方案2】:

    首先:对于您的删除例程,您的 where 子句应该只包含主键。

    第二:对于你的更新程序,在你有工作代码之前不要尝试优化。事实上,在您可以分析您的应用程序并查看瓶颈所在之前,不要尝试进行优化。我可以肯定地告诉你,更新一行的一列和更新一行的所有列的速度几乎相同。在 DBMS 中需要时间的是 (1) 找到您将写入数据的磁盘块和 (2) 锁定其他写入器,以便您的写入保持一致。最后,编写只更新需要更改的列所需的代码通常更难做,也更难维护。如果您真的想变得挑剔,则必须比较找出哪些列更改的速度与仅更新每一列的速度。如果您全部更新它们,则不必阅读其中任何一个。

    第三:我倾向于为每个检索路径编写一个存储过程。在您的示例中,我将按主键创建一个,按每个外键创建一个,然后根据应用程序中的需要为每个新访问路径添加一个。敏捷;不要编写你不需要的代码。我也同意使用视图而不是存储过程,但是,您可以使用存储过程返回多个结果集(在某些版本的 MSSQL 中)或将行更改为列,这可能很有用。

    例如,如果您需要按主键获取 7 行,您有一些选择。您可以调用七次按主键获取一行的存储过程。如果您在所有呼叫之间保持连接打开,这可能会足够快。如果您知道一次不需要超过一定数量(例如 10 个)的 ID,则可以编写一个包含 where 子句的存储过程,例如“and ID in (arg1, arg2, arg3...)”,然后确保未使用的参数设置为 NULL。如果您决定需要生成动态 SQL,我不会为存储过程而烦恼,因为 TSQL 与任何其他语言一样容易出错。此外,使用数据库进行字符串操作不会给您带来任何好处——它几乎总是您的瓶颈,因此没有必要为数据库提供更多不必要的工作。

    【讨论】:

    • 第一:你有可能是真理。检查“在您获取数据后和保存数据之前另一个用户更改了数据”对于删除毫无价值。第二:我考虑更新一个大的列。像“ntext”。在某些情况下,可能会有带有图像的 RTF 文本。
    • 第三:当我想从 tblProduct 中获取 7 行时。使用各种“@ProductID”值调用“Read_Product”7 次的好方法还是我应该做其他事情?
    • 对于 ntext,数据通常存储在与行的其余部分不同的块中,所以是的,在这种情况下,您可能希望有一个不传递 ntext 列的存储过程.尽管如此,将一个 ntext 值与传入的值进行比较比仅覆盖要慢。
    • 这需要测试和分析。如果你知道你永远不会有超过某个数字(比如 10),你可以编写一个接受 10 个 ID 值的存储过程,如果这次只需要 7 个,则使用 NULL。 select 语句将包含类似:“and ID in (arg1, arg2, arg3...)”
    • 不过,有时,如果您在所有 7 次调用之间保持连接打开,则使用 1 ID 调用存储过程 7 次会更快。让存储过程编写动态 SQL 是没有意义的。您在 TSQL 中出现错误的可能性与在任何其他语言中一样。
    【解决方案3】:

    我不同意创建插入/更新/选择存储过程是“最佳实践”。除非您的整个应用程序是用 SP 编写的,否则请在应用程序中使用数据库层来处理这些 CRUD 活动。更好的是,使用 ORM 技术为您处理它们。

    【讨论】:

    • 同意 - SP 最适合更复杂的操作,更有效/更安全/更容易直接在数据库上执行的操作。对于简单的 CRUD 语句,SP 几乎没有任何好处,最好使用中间层。
    【解决方案4】:

    我的建议是,您不要尝试创建一个存储过程来执行您现在或将来可能需要执行的所有操作。如果您需要根据表的主键检索一行,请编写一个存储过程来执行此操作。如果您需要搜索满足一组条件的所有行,那么找出该条件可能是什么并编写一个存储过程来执行此操作。

    如果您尝试编写解决所有可能问题而不是特定问题集的软件,您通常无法提供任何有用的东西。

    【讨论】:

      【解决方案5】:

      您的选择存储过程可以按如下方式完成,只需要一个存储过程,但 where 子句中需要任意数量的不同项目。传入任何一个参数或参数组合,您将获得所有匹配的项目 - 因此您只需要一个存储过程。

      Create sp_ProductSelect
      (
       @ProductID int = null,
       @IDProductType int = null,
       @ProductName varchar(50) = null,
       @ProductCode varchar(10) = null,
       ...
       @Volume int = null
      )
      AS
      SELECT ProductID, IDProductType, ProductName, ProductCode, Weight, Volume FROM tblProduct'  
      Where
        ((@ProductID is null) or (ProductID = @ProductID)) AND
        ((@ProductName is null) or (ProductName = @ProductName)) AND
        ...
        ((@Volume is null) or (Volume= @Volume))
      

      【讨论】:

      • 但是通过这种方式我可以只过滤平等。
      • 我会使用它来获取数据子集,然后允许使用代码中的数据视图进行动态过滤。这将为您提供最灵活的解决方案,而不会带来潜在的安全风险。
      【解决方案6】:

      在 SQL 2005 中,它支持 nvarchar(max),它有 2G 的限制,但实际上接受了普通 nvarchar 上的所有字符串操作。您可能想测试这是否适合您在第一种方法中的需要。

      【讨论】:

        猜你喜欢
        • 2013-04-18
        • 2010-10-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-29
        • 2019-12-01
        • 1970-01-01
        • 2018-12-31
        相关资源
        最近更新 更多