为大数据表找到合适的索引

Find the right Index for big datatable

我有大量数据table,其中包含大量物联网数据。 table 是使用 ef-core codefirst 创建的:

public class IoTData : Auditierbar<IoTData, Guid>
    {
        public Guid IoTDeviceId { get; set; }
        public Guid IoTDeviceInternalId { get; set; }
        public TimeSpan Zeit { get; set; }

        public string EventMessage { get; set; }
        public DateTime Timestamp { get; set; }
        public int Input1 { get; set; }
        public int Input2 { get; set; }
        public DateTime? SendData { get; set; }
        public string IId { get; set; }
        public string DeviceId { get; set; }
        public bool TestData { get; set; }

        public virtual IoTDevice IoTDevice { get; set; }
        public virtual ICollection<IoTData_Rueckmeldung> IoTData_Feedbacks { get; set; }
        public bool InputStatus1 { get; set; }
        public bool InputStatus2 { get; set; }
    }

    public class IoTDataConfiguration : AuditierbarConfiguration<IoTData, Guid>
    {
        public override void Configure(EntityTypeBuilder<IoTData> builder)
        {
            base.Configure(builder);

            builder.ToTable("MDE_IoTData");
            builder.HasIndex(r => new { r.IoTDeviceId });
            builder.HasIndex(r => new { r.UnternehmenId, r.Datum });
            // builder.HasIndex(r => new { r.UnternehmenId, r.Datum, r.Zeit });
            builder.HasIndex(r => new { r.UnternehmenId, r.Datum, r.IoTDeviceId });
            //builder.HasIndex(r => new { r.UnternehmenId, r.Datum, r.Zeit, r.IoTDeviceId });
            builder.HasIndex(r => new { r.UnternehmenId, r.IoTDeviceInternalId }).IsUnique();
            builder.Property(r => r.Zeit).IsRequired().HasColumnType("Time(0)").HasDefaultValue(default);

            builder.Property(r => r.EventMessage).IsRequired();
            builder.Property(r => r.Datum).IsRequired().HasColumnType("Date").HasDefaultValueSql("GetUtcDate()");

        }

可以看到,已经创建了几个索引。您可以在此处查看我的查询:

declare @__companyId_0 as uniqueidentifier ='03d7fdd2-....-....-......'
declare @__starttime_Date_1 as date= '2021-9-30'  
declare @__endtime_Date_2 as date ='2021-10-01'
--declare @__starttime_TimeOfDay_3 as time ='22:00:00' 
--declare @__endtime_TimeOfDay_4 as time ='22:00:00' 
    
SELECT [m].[IoTDeviceId], [m].[Datum], [m].[Zeit], [m].[Input1], [m].[Input2], [m].[Timestamp]
FROM [MDE_IoTData] AS [m]
WHERE ((([m].[UnternehmenId] = @__companyId_0) AND 
([m].[Datum] >= @__starttime_Date_1)) AND 
([m].[Datum] <= @__endtime_Date_2)) AND 

[m].[IoTDeviceId] in(
'E87B3284-....-....-......',
'A363FE26-....-....-......',
'97924216-....-....-......',
'C8C50B8C-....-....-......',
'93F80183-....-....-......',
'F3572F35-....-....-......',
'6B19A81B-....-....-......',
'A17CBB46-....-....-......') 

我想,这个索引适合这个查询

builder.HasIndex(r => new { r.UnternehmenId, r.Datum, r.IoTDeviceId });

但我有时会遇到 sql-超时异常。难道是我的索引不对? 每个 IoTDeviceId 最多可以有 1440 行。所以在那个查询中(两天的数据)我预计最多。 11520 行,需要 5 秒。如果日期范围更大,例如。 14天,查询耗时一分多钟

有什么想法吗?

更新

查询计划: https://www.brentozar.com/pastetheplan/?id=B1CHVrOVF

Table-架构

CREATE TABLE [dbo].[MDE_IoTData](
    [Id] [uniqueidentifier] NOT NULL,
    [Erstellt] [datetime2](7) NOT NULL,
    [Datum] [date] NOT NULL,
    [UnternehmenId] [uniqueidentifier] NOT NULL,
    [IoTDeviceId] [uniqueidentifier] NOT NULL,
    [Zeit] [time](0) NOT NULL,
    [EventMessage] [nvarchar](max) NOT NULL,
    [Input1] [int] NOT NULL,
    [Input2] [int] NOT NULL,
    [IoTDeviceInternalId] [uniqueidentifier] NOT NULL,
    [SendData] [datetime2](7) NULL,
    [Timestamp] [datetime2](7) NOT NULL,
    [InputStatus1] [bit] NOT NULL,
    [InputStatus2] [bit] NOT NULL,
 CONSTRAINT [PK_MDE_IoTData] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

ALTER TABLE [dbo].[MDE_IoTData] ADD  DEFAULT (getutcdate()) FOR [Erstellt]
GO

ALTER TABLE [dbo].[MDE_IoTData] ADD  DEFAULT (getutcdate()) FOR [Datum]
GO

ALTER TABLE [dbo].[MDE_IoTData] ADD  DEFAULT (N'') FOR [EventMessage]
GO

ALTER TABLE [dbo].[MDE_IoTData] ADD  DEFAULT ((0)) FOR [Input1]
GO

ALTER TABLE [dbo].[MDE_IoTData] ADD  DEFAULT ((0)) FOR [Input2]
GO

ALTER TABLE [dbo].[MDE_IoTData] ADD  DEFAULT ('00000000-0000-0000-0000-000000000000') FOR [IoTDeviceInternalId]
GO

ALTER TABLE [dbo].[MDE_IoTData] ADD  DEFAULT ('0001-01-01T00:00:00.0000000') FOR [Timestamp]
GO

ALTER TABLE [dbo].[MDE_IoTData] ADD  DEFAULT (CONVERT([bit],(0))) FOR [InputStatus1]
GO

ALTER TABLE [dbo].[MDE_IoTData] ADD  DEFAULT (CONVERT([bit],(0))) FOR [InputStatus2]
GO

ALTER TABLE [dbo].[MDE_IoTData]  WITH CHECK ADD  CONSTRAINT [FK_MDE_IoTData_Global_Unternehmen_UnternehmenId] FOREIGN KEY([UnternehmenId])
REFERENCES [dbo].[Global_Unternehmen] ([Id])
GO

ALTER TABLE [dbo].[MDE_IoTData] CHECK CONSTRAINT [FK_MDE_IoTData_Global_Unternehmen_UnternehmenId]
GO

ALTER TABLE [dbo].[MDE_IoTData]  WITH CHECK ADD  CONSTRAINT [FK_MDE_IoTData_MDE_IoTDevice_IoTDeviceId] FOREIGN KEY([IoTDeviceId])
REFERENCES [dbo].[MDE_IoTDevice] ([Id])
GO

ALTER TABLE [dbo].[MDE_IoTData] CHECK CONSTRAINT [FK_MDE_IoTData_MDE_IoTDevice_IoTDeviceId]
GO

此查询的最佳索引最有可能

(UnternehmenId, Datum) INCLUDE (IoTDeviceId, IoTDeviceId, Zeit, Input1, Input2, [Timestamp])

这称为 覆盖 索引,因为它覆盖了查询中的所有列。最好将这些列保留在 INCLUDE 中,但是您可能需要使用手动 SQL 语句来执行此操作,而不是使用 HasIndex()