更快地插入两个相关表

时间:2015-09-05 10:55:41

标签: sql-server database auto-increment sql-insert

我有持续发送一些数据的设备,我将它保存到数据库中。我正在寻找快速的方法。现在数据被发送到msmq,然后我正在进行多次插入,但这需要很长时间。我正在等待插入数据记录,获取ID,然后我在DataAttachment中插入相关记录。架构如下所示:

CREATE TABLE [dbo].[Data](
  [ID] [int] IDENTITY(1,1) NOT NULL,
  [IDDevice] [int] NOT NULL,
  [Time] [datetime] NOT NULL,
  [Value] [varchar](20) NOT NULL, 
...

CREATE TABLE [dbo].[DataAttachment](
  [IDData] [int] IDENTITY(1,1) NOT NULL,
  [AttachmentType] [int] NOT NULL,
  [FileName] [datetime] NOT NULL,
  [FileContent] [varchar](20) NOT NULL

我正在考虑准备多个插入,我会在每个插入的数据后获得最后插入的id,并使用它来插入附件。但我认为这不是一个好主意,如果已经插入另一个表,我会得到错误的ID。

另一种选择是从Time和Value生成一个哈希值,该哈希值可以插入DataAttachment和Data中。然后就可以在Data和DataAttachment中插入多个数据而不会出现问题,然后我只需通过哈希值搜索数据来更改DataAttachment中的IDData。

我想请教一个如何快速插入的建议。

3 个答案:

答案 0 :(得分:1)

您的场景是使用Sequence对象(SQLServer 2012及更高版本)的完美案例。我知道,因为我已经将它用于你正在尝试做的事情 - 直到表名和DDL非常相似。

序列对象允许您创建唯一ID,用于dbo.Data表的ID列和dbo.DataAttachment表中的相关IDData(外键)列。它避免在将相关附件插入dbo.DataAttachment之前等待dbo.Data插入。它可以让您避免使用GUIDS及其相关问题。它允许轻松批量插入以加快性能。

这里是Sequence对象的一个​​很棒的链接: https://msdn.microsoft.com/en-us/library/ff878091.aspx

基本方法是创建一个:

  1. 查找要插入的所有新数据记录(从您的某个msmq表中猜测),然后将它们插入临时表。在临时表上,您应该有一个ID列,用于获取[your_sequence_object]的NEXT VALUE。该ID将是插入新数据行的主键。您需要在数据表中禁用此列的Identity属性。

  2. 接下来,创建另一个临时表,该表将保存每个数据行的相关附件。创建此临时表时,您将加入上面步骤1中创建的数据临时表和您从中检索DataAttachment行的任何源。这将允许您使用步骤1数据临时表中的ID列作为DataAttachment中的外键列IDData。

  3. 对于DataAttachment表的ID列(此表上的主键),您可以将其保留为原样,并保留Identity属性,在这种情况下,您将其保留在DataAttachment临时表之外,而不是显式插入它。或者,您可以使用不同的序列对象为其生成唯一值。

    1. 将数据临时表的内容插入dbo.Data表,然后将DataAttachment临时表的内容插入dbo.DataAttachment表。

答案 1 :(得分:0)

你需要一个外键。

CREATE TABLE [dbo].[DataAttachment](
  [ID] [int] IDENTITY(1,1) NOT NULL,
  [IDData] int FOREIGN KEY REFERENCES dbo.Data(ID),
  [AttachmentType] [int] NOT NULL,
  [FileName] [datetime] NOT NULL,
  [FileContent] [varchar](20) NOT NULL
)

您正确地注意到,在知道ID之前,您无法创建外键。

您有两种选择。

一种是使用可以在客户端选择的ID(唯一密钥)的数据类型。为此,您需要一种方法让客户端生成唯一的ID,该ID不会与已存储在服务器上的(未知)值冲突。

执行此操作的典型方法是使用专为此目的而设计的uniqueidentifier(GUID)。然后您可以在客户端分配它们,这样您就可以提前知道它们是唯一的。另一种方法是使用随机的64位整数(这通常是Oracle用于此目的)。

第二种方法是在单个消息中传递所有数据(无论是单个存储过程调用,还是单个MSMQ消息或其他)。然后,存储过程(或消息的任何进程)可以分配唯一标识符,执行所有插入,并将分配的ID返回给调用者。

答案 2 :(得分:0)

如果您使用的是支持表值参数的客户端API,则可以使用以下示例中的技术在单个往返中插入数据行和相关的DataAttachments。在.NET中,TVP行可以作为DataTable,DataRow数组或SqlDataRecord类型的IEnumrable传递。有关示例C#代码,请参阅http://www.dbdelta.com/maximizing-performance-with-table-valued-parameters/

如果在客户端分配密钥以关联Data和DataAttachment行,则此技术也可以扩展为插入多个Data行。然后,您可以将两者都作为TVP传递。相关键需要在两种TVP类型中都是列,但不一定存储在表中。

CREATE TYPE DataAttachmentType AS TABLE(
  [AttachmentType] [int] NOT NULL,
  [FileName] [datetime] NOT NULL,
  [FileContent] [varchar](20) NOT NULL
);
GO

CREATE PROC dbo.InsertDataAndAttachments
    @IDDevice int
  , @Time datetime
  , @Value varchar(20)
  , @DataAttachments dbo.DataAttachmentType READONLY
AS
SET NOCOUNT, XACT_ABORT ON;

DECLARE @IDData int;

BEGIN TRY

    BEGIN TRAN;

    INSERT  INTO dbo.Data
            ( IDDevice, Time, Value )
    VALUES  ( @IDDevice, @Time, @Value );

    SET @IDData = SCOPE_IDENTITY();

    INSERT  INTO dbo.DataAttachment
            ( IDData
            , AttachmentType
            , FileName
            , FileContent
            )
            SELECT  @IDData
                  , AttachmentType
                  , FileName
                  , FileContent
            FROM    @DataAttachments;

    COMMIT;
END TRY
BEGIN CATCH

    IF @@TRANCOUNT > 0 ROLLBACK;

    THROW;

END CATCH;
GO