Question

We have a User table with a GUID aka uniqueidentifier as the PK. Nearly every other table in the database ties back to this table with 4 FK references. When I look at the DB diagram it looks like a 100 lane highway coming out of the User table because of the CreatedBy, CreatedByProxy, UpdatedBy, UpdatedByProxy foreign keys.

I was brought onto this project after it was already past inception and in production already, and with performance issues.

I was wondering if this DB pattern will cause major growing pains when the user list starts to grow. Are we going to run into more performance issues in the future because of this, or if we create an index will keeping the foreign keys cause the indexes to be huge. I just don't remember a website that I've worked on before that had the foreign keys to this extent before, and I'm worried about future proofing/fixing it. I'm really just trying to justify whether or not to keep or remove the foreign keys or modify the structure so that

User Table:

    CREATE TABLE [dbo].[aspnet_Users](
        [ApplicationId] [uniqueidentifier] NOT NULL,
        [UserId] [uniqueidentifier] NOT NULL, -- ***** Here is the PK
        [UserName] [nvarchar](256) NOT NULL,
        [LoweredUserName] [nvarchar](256) NOT NULL,
        [MobileAlias] [nvarchar](16) NULL,
        [IsAnonymous] [bit] NOT NULL,
        [LastActivityDate] [datetime] NOT NULL,
    PRIMARY KEY NONCLUSTERED 
    (
        [UserId] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]

One other Table that references User:

CREATE TABLE [dbo].[Email](
    [EmailId] [int] IDENTITY(1,1) NOT NULL,
    [PersonId] [int] NULL,
    [InstitutionId] [int] NULL,
    [EmailTypeId] [int] NOT NULL,
    [EmailAddress] [varchar](254) NOT NULL,
    [IsFlaggedImportant] [bit] NOT NULL,
    [IsDistrictRecord] [bit] NOT NULL,
    [IsActive] [bit] NOT NULL,
    [Created] [datetime] NOT NULL,
    [CreatedBy] [uniqueidentifier] NOT NULL, -- ***** FK 1
    [Proxy] [uniqueidentifier] NULL, -- ***** FK 2
    [Updated] [datetime] NULL,
    [UpdatedBy] [uniqueidentifier] NULL, -- ***** FK 3
    [UpdateProxy] [uniqueidentifier] NULL, -- ***** FK 4
 CONSTRAINT [PK_Email] PRIMARY KEY CLUSTERED 
(
    [EmailId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[Email] ADD  CONSTRAINT [DF_Email_IsPrimary]  DEFAULT ((0)) FOR [IsFlaggedImportant]
GO

ALTER TABLE [dbo].[Email] ADD  CONSTRAINT [DF_Email_IsDistrictRecord]  DEFAULT ((0)) FOR [IsDistrictRecord]
GO

ALTER TABLE [dbo].[Email] ADD  CONSTRAINT [DF_Email_IsActive]  DEFAULT ((0)) FOR [IsActive]
GO

ALTER TABLE [dbo].[Email] ADD  CONSTRAINT [DF_Email_Created]  DEFAULT (getdate()) FOR [Created]
GO

ALTER TABLE [dbo].[Email]  WITH CHECK ADD  CONSTRAINT [FK_Email_CreatedByUser] FOREIGN KEY([CreatedBy])
REFERENCES [dbo].[aspnet_Users] ([UserId])
GO

ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_CreatedByUser]
GO

ALTER TABLE [dbo].[Email]  WITH CHECK ADD  CONSTRAINT [FK_Email_EmailType] FOREIGN KEY([EmailTypeId])
REFERENCES [dbo].[EmailType] ([EmailTypeId])
GO

ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_EmailType]
GO

ALTER TABLE [dbo].[Email]  WITH CHECK ADD  CONSTRAINT [FK_Email_Institution] FOREIGN KEY([InstitutionId])
REFERENCES [dbo].[Institution] ([InstitutionId])
GO

ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_Institution]
GO

ALTER TABLE [dbo].[Email]  WITH CHECK ADD  CONSTRAINT [FK_Email_Person] FOREIGN KEY([PersonId])
REFERENCES [dbo].[Person] ([PersonId])
GO

ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_Person]
GO

ALTER TABLE [dbo].[Email]  WITH CHECK ADD  CONSTRAINT [FK_Email_ProxyByUser] FOREIGN KEY([Proxy])
REFERENCES [dbo].[aspnet_Users] ([UserId])
GO

ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_ProxyByUser]
GO

ALTER TABLE [dbo].[Email]  WITH CHECK ADD  CONSTRAINT [FK_Email_ProxyUpdateByUser] FOREIGN KEY([UpdateProxy])
REFERENCES [dbo].[aspnet_Users] ([UserId])
GO

ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_ProxyUpdateByUser]
GO

ALTER TABLE [dbo].[Email]  WITH CHECK ADD  CONSTRAINT [FK_Email_UpdatedByUser] FOREIGN KEY([UpdatedBy])
REFERENCES [dbo].[aspnet_Users] ([UserId])
GO

ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [FK_Email_UpdatedByUser]
GO

ALTER TABLE [dbo].[Email]  WITH CHECK ADD  CONSTRAINT [CK_Email_Person_Or_Institution] CHECK  (([PersonId] IS NOT NULL AND [InstitutionId] IS NULL OR [PersonId] IS NULL AND [InstitutionId] IS NOT NULL))
GO

ALTER TABLE [dbo].[Email] CHECK CONSTRAINT [CK_Email_Person_Or_Institution]
GO
Was it helpful?

Solution

I was brought onto this project after it was already past inception and in production already, and with performance issues.

You don't say what those performance issues are, so I'll confine my remarks to the foreign keys.

When I look at the DB diagram it looks like a 100 lane highway coming out of the User table because of the CreatedBy, CreatedByProxy, UpdatedBy, UpdatedByProxy foreign keys.

When I see columns like that, I have to ask whether they contain information about the business entity--a person's email address in this case--or information about the row that entity happens to inhabit.

It looks like they contain information about the row. (But I could be wrong.)

If they do contain information about the row, and they're not needed in most queries, you can move them to another table. If you move them, you have to be more careful about inserting rows into dbo.Email.

CREATE TABLE [dbo].[Email](
    [EmailId] [int] IDENTITY(1,1) NOT NULL,
    [PersonId] [int] NULL,
    [InstitutionId] [int] NULL,
    [EmailTypeId] [int] NOT NULL,
    [EmailAddress] [varchar](254) NOT NULL,
    [IsFlaggedImportant] [bit] NOT NULL,
    [IsDistrictRecord] [bit] NOT NULL,
    [IsActive] [bit] NOT NULL,
    CONSTRAINT [PK_Email] PRIMARY KEY CLUSTERED ([EmailID])
);

CREATE TABLE [dbo].[Email_audit](
    [EmailID] [int] PRIMARY KEY REFERENCES [Email] ([EmailID]),
    [Created] [datetime] NOT NULL,
    [CreatedBy] [uniqueidentifier] NOT NULL, -- ***** FK 1
    [Proxy] [uniqueidentifier] NULL,         -- ***** FK 2
    [Updated] [datetime] NULL,
    [UpdatedBy] [uniqueidentifier] NULL,     -- ***** FK 3
    [UpdateProxy] [uniqueidentifier] NULL    -- ***** FK 4
);

These kinds of tables are commonly used to provide some kind of audit trail. Whether you can cascade deletes is application-dependent. In some apps, you need to store the email address here instead of the email id number, and use no foreign key references. (This allows deleting rows from dbo.Email while retaining some information about what's happened to the row.)

Moving these columns reduces the width of a row in dbo.Email by about 80 bytes, not counting overhead. That usually improves performance of SELECT statements in the tables they're moved from. (Narrower rows; more rows per page.)

Moving these columns complicates inserting and updating rows, though. All inserts and updates have to hit two tables.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top