I'm creating a SaaS application and I'm using the Multi DB - Multi-Tenant database model, meaning multiple tenants can share the same database. Tenants will be placed in databases depending on their usage or activities. Now I'm just wondering about how I should store the data in databases for the best performance. What should be the primary keys and/or indexes of the tables?
I have the tenant Id on every single table in the databases.
Tenant Table:
tenant_id [INT] IDENTITY(1,1) NOT NULL,
name [VARCHAR] NOT NULL,
...
PRIMARY KEY CLUSTERED (tenant_id)
User Table:
user_id [INT]nIDENTITY(1,1) NOT NULL,
name [VARCHAR] NOT NULL,
...
PRIMARY KEY CLUSTERED (user_id)
FOREIGN KEY (tenant_id)
Order Table:
order_id [INT]IDENTITY(1,1) NOT NULL,
date [DATETIME] NOT NULL,
...
PRIMARY KEY CLUSTERED (order_id)
FOREIGN KEY (tenant_id)
Not sure if this is a good design. What should be the primary key and/or indexes of the tables?
Based on the fact that every single query in the app should have the tenant_id.
option 1:
PRIMARY KEY CLUSTERED (order_id) and NON CLUSTERED INDEX on (tenant_id)
option 2:
PRIMARY KEY CLUSTERED (order_id) and NON CLUSTERED INDEX on (tenant_id, order_id)
option 3:
PRIMARY KEY NON CLUSTERED (order_id) and CLUSTERED INDEX on (tenant_id, order_id)
option 4:
PRIMARY KEY CLUSTERED (tenant_id, order_id)
I would greatly appreciate your suggestions or advice. Thanks
Related
Consider the following table...
CREATE TABLE [dbo].[Alerts]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[I18NMessageKey] [uniqueidentifier] NOT NULL
PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO
and the following table...
CREATE TABLE [dbo].[I18NMessages]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[Key] [uniqueidentifier] NOT NULL,
[Culture] [nvarchar](200) NOT NULL,
[Message] [nvarchar](max) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO
I would like to add a foreign key constraint to table [Alerts] on the column [I18NMessageKey] to refer to many records in table [I18NMessages].
Is this possible without a third table?
The [I18NMessages] table holds the same message for the [Key] but in different languages depending on [Culture]. The relationship between [Alerts] and [I18NMessages] doesn't care about the culture. The resolution of [Culture] depends on the user at runtime.
In SQL Server, the uniqueness of the referenced key column(s) must be enforced by a primary key, unique constraint, or unique index. You need a third table with a unique I18NMessageKey column key to enforce referential integrity.
You can create a trigger and implement custom business logic
Suppose I have the following table definition with a composite primary key:
create table [dbo].[CustomerRequests] (
[CustomerId] int not null,
[RequestId] int not null,
constraint [PK_CustomerRequests] primary key clustered ([CustomerId] asc, [RequestId] asc),
constraint [FK_CustomerRequests_Customers] foreign key ([CustomerId]) references [dbo].[Customers] ([CustomerId]),
constraint [FK_CustomerRequests_Requests] foreign key ([RequestId]) references [dbo].[Requests] ([RequestId])
);
When I update the model to include this table, Entity Framework fails to generate the entity class. Is this due to the composite primary key? Is there any way to make Entity Framework generate the entity class?
Gert Arnold's comment pointed me in the right direction, as did this answer.
Once I added another column besides the two primary keys, Entity Framework generated the entity. Here is a sample table definition for which EF Database First will create an entity:
create table [dbo].[CustomerRequests] (
[CustomerId] int not null,
[RequestId] int not null,
[TimestampUtc] datetime not null,
constraint [PK_CustomerRequests] primary key clustered ([CustomerId] asc, [RequestId] asc),
constraint [FK_CustomerRequests_Customers] foreign key ([CustomerId]) references [dbo].[Customers] ([CustomerId]),
constraint [FK_CustomerRequests_Requests] foreign key ([RequestId]) references [dbo].[Requests] ([RequestId])
);
What is the best way to make a simple many-to-many cross reference table which contains nothing but two columns which are themselves primary keys in other tables?
Does anyone have concrete evidence for or against creating a table with a single unique index, but no primary key? (Alternatives are detailed below).
Put another way: How does SQL Server internally uniquely identifies rows a) that have a primary key and b) that do not have a primary key?
In detail:
Given the input tables:
CREATE TABLE Foo ( FooID bigint identity(1,1) not null primary key, other stuff... )
CREATE TABLE Bar ( BarID bigint identity(1,1) not null primary key, other stuff... )
The three basic options are (in all cases assume a foreign key is created on the FooID and BarID columns):
-- Option 1: Compound primary key
CREATE TABLE FooBarXRef (
FooID bigint not null
, BarID bigint not null
, PRIMARY KEY ( FooID, BarID )
, CONSTRAINT FK... etc
)
-- Option 2: Independent primary key + unique index
CREATE TABLE FooBarXRef (
FooBarXRefID bigint identity(1,1) not null primary key
, FooID bigint not null
, BarID bigint not null
, CONSTRAINT FK... etc
);
CREATE UNIQUE INDEX I_FooBarXRef_FooBar ON FooBarXRef ( FooID, BarID );
-- Option 3: Unique index, no explicit primary key:
CREATE TABLE FooBarXRef (
FooID bigint not null
, BarID bigint not null
, CONSTRAINT FK... etc
);
CREATE UNIQUE INDEX I_FooBarXRef_FooBar ON FooBarXRef ( FooID, BarID );
Does having a separate identity PK on the xref table to be redundant; that may needlessly introduces another layer of constraint checking on the database engine?
On the other hand are multi-column primary keys problematic? With a proposed solution to have the xref table contain only the two foreign keys, and define a unique index on those columns, but not define a primary key at all... ?
I suspect that doing so will cause SQL Server to create an internal primary key for the purposes of uniquely identifying each row, thus yielding the same redundant constraints as if a primary key were defined explicitly--but I have no proof or documentation to support this. Other questions and answers suggest that there is not an internal primary key by default (i.e. no equivalent to the Oracle ROWID); as the %%physloc%% is an indicator of where a row is currently stored and thus is subject to change. My intuition is that the engine must create something to uniquely identify a row in order to implement cursors, transactions, and concurrency.
The concept of a primary key is really about relational theory; maintaining referential integrity by building relationships across multiple tables. The SQL Server engine, by default, creates a unique clustered index when a primary key is built (assuming a clustered index doesn't exist at the moment).
It's this clustered index that defines a unique row at the leaf level. For tables that have a non-unique clustered index, SQL Server creates a 4byte "uniquifier" to to the end of your key.
TestTable1 Primary Key
TestTable2 Primary Key & Unique Non-Clustered
TestTable3 Unique Clustered
TestTable4 Primary Clustered (same as Table1 & Table3, since a primary key CAN be defined on a non-clustered index I prefer this to always define which structure I want).
TestTable2 is redundant, it's create a unique clustered index to store all the records at it's leaf level. It's then creating a unique non-clustered index to enforce uniqueness once again. Any changes on the table will hit the clustered and then the non-cluster.
TestTable1, TestTable3, TestTable4 are a tie in my book, a unique clustered index structure is created on all. There is no physical difference in the way records are stored on a page.
However for SQL Server Replication, all replicated tables required a primary key. If your'll be using Replication in the future you may want to make sure all your unique clustered indexes are primary keys as well.
I seem to be unable to paste in my verifying scripts, so here they are on hastebin.
http://hastebin.com/qucajimixi.vbs
Well, it all depends on the requirement. As far as I know
PRIMARY KEY= UNIQUE KEY+NOT NULL key
What this tells you is that you can have multiple
NOT NULL UNIQUE INDEXES(NON CLUSTERED)
but
CANNOT HAVE MULTIPLE PRIMARY KEYS IN A TABLE( CLUSTERED).
I am a huge believer of Relational database model and working with the PRIMARY-FOREIGN KEYS relationships. DB replication requires you to have Primary Key on a table ; therefore, it is always a good practice to create Primary Key instead of UNIQUE keys for your table.
The "Create Table" grammar rather clearly does not allow me to specify a clustered foreign key constraint. In other words, this is illegal:
--keyword CLUSTERED must be removed before this will execute...
CREATE TABLE [Content](
[ID] [int] NOT NULL CONSTRAINT PK_Content_ID PRIMARY KEY,
ContentDefID int NOT NULL CONSTRAINT FK_Plugin_ContentDef FOREIGN KEY CLUSTERED REFERENCES ContentDef(ID)
)
GO
But I don't understand why it is illegal. ISTM that clustering a foreign-key would facilitate performance of paged-lookups. In other words, "give me child items 80 through 140 of parent ID 20".
Is there a rationale for this?
Update
Based on Oded and Tvanfosson feedback, I've found that the following works:
CREATE TABLE [Content](
[ID] [int] NOT NULL CONSTRAINT PK_Content_ID PRIMARY KEY,
ContentDefID int NOT NULL UNIQUE CLUSTERED CONSTRAINT FK_ContentDefContent FOREIGN KEY REFERENCES ContentDef(ID)
)
GO
But the above causes more problems than it solves. First, a "UNIQUE" foreign key forces my relationship to be one-to-one which I don't want. Second, this only works because it represents the creation of two separate constraints, rather than a single CLUSTERED FOREIGN KEY.
But this investigation is getting me closer to my answer. Evidently clustered indexes MUST be unique, as stated here on SO. Quoting:
If the clustered index is not a unique index, SQL Server makes any duplicate keys unique by adding an internally generated value called a uniqueifier
In particular, I think this answer covers it.
As others have explained, the clustered index does not have to be the primary key but it either has to be unique or SQL-Server adds a (not shown) UNIQUIFIER column to it.
To avoid this, you can make the clustered index unique by explicitly adding the primary key column to the clustered index, like below. The index will then be avaialbel to be used by the foreign key constraints (and for queries, like joining the two tables).
Notice, that as #Martin Smith has explained, the concepts of CONSTRAINT and INDEX are different. And the various DBMSs implement these in different ways. SQL-Server automatically creates an index for some constraints, while it doesn't for foreign key constraints. It's advised though to have an index that the constraint can use (when deleting or updating in the referenced table):
CREATE TABLE Content(
ID int NOT NULL,
ContentDefID int NOT NULL,
CONSTRAINT PK_Content_ID
PRIMARY KEY NONCLUSTERED (ID),
CONSTRAINT CI_Content
UNIQUE CLUSTERED (ContentDefID, ID),
CONSTRAINT FK_Plugin_ContentDef
FOREIGN KEY (ContentDefID) REFERENCES ContentDef(ID)
) ;
Is there a rationale for this?
You might as well ask why you can't create a CLUSTERED check constraint or a CLUSTERED default constraint.
A foreign key simply defines a logical constraint and has no indexes automatically created for it in SQL Server (this only happens for UNIQUE or PRIMARY KEY constraints). It is always the case in SQL Server that if you want the FK columns indexed you need to run a CREATE INDEX on the relevant column(s) yourself.
Therefore the concept of a CLUSTERED FOREIGN KEY doesn't make any sense. You can of course create a CLUSTERED INDEX on the columns making up the FK though as you indicate in your question.
You can only have one clustered index on a table. By default this will be the primary key column.
There are ways to change this - you will need to use PRIMARY KEY NONCLUSTERED and UNIQUE CLUSTERED FOREIGN KEY.
It seems you're conflating the ideas of the clustered index with keys (either primary or foreign). Why not just make the table and then specify its clustered index afterwards? (code copied from your first example and changed as little as possible)
CREATE TABLE [Content](
[ID] [int] NOT NULL CONSTRAINT PK_Content_ID PRIMARY KEY NONCLUSTERED,
ContentDefID int NOT NULL CONSTRAINT FK_Plugin_ContentDef FOREIGN KEY REFERENCES ContentDef(ID)
)
GO
CREATE CLUSTERED INDEX IX_Content_Clustered on Content(ContentDefID)
There's no need for you to make the clustered index unique
Is it possible to create a clustered index from a create table statement in SQL Server 2008 that is not a primary key?
The purpose of this is for a table in SQL Azure, so it is not an option for me to first create the table, and then create the clustered index on the table.
Edit: Apparently it was FluentMigrator that was causing my problems, it's version table does not have a clustered index so it was erroring trying to create the versioning table not my table.
Yes, it is possible to create a clustered index that is not the primary key. Just use a CREATE CLUSTERED INDEX statement.
CREATE TABLE dbo.myTable (
myTableId int PRIMARY KEY NONCLUSTERED
myColumn int NOT NULL
)
CREATE CLUSTERED INDEX myIndex ON dbo.myTable(myColumn)
Prior to version Azure SQL Database v12, you had to have a clustered index before you could insert any data to a table. As of Azure SQL Database v12, heaps (tables without a clustered index) are now supported.
If your database was created prior to June 2016, here are the instructions for upgrading to version 12.
CREATE TABLE dbo.Table_1
(
Id int NOT NULL IDENTITY (1, 1) PRIMARY KEY NONCLUSTERED,
SomeOtherUniqueColumn int NOT NULL CONSTRAINT Item4 UNIQUE CLUSTERED
) ON [PRIMARY]
note the specification of nonclustered on the primary key
This will still work.
CREATE TABLE dbo.Table_1
(
SomeOtherUniqueColumn int NOT NULL CONSTRAINT Item4 UNIQUE CLUSTERED
) ON [PRIMARY]
The code below is compatible with Azure. It creates a primary key non-clustered and a clustered index in a single create table statement. This syntax also allows for specifying more than one column in your key.
CREATE TABLE MyTable (
ID uniqueidentifier NOT NULL,
UserID uniqueidentifier NOT NULL,
EntryDate DATETIME NOT NULL,
CONSTRAINT PK_MyPrimaryKey_Name PRIMARY KEY NONCLUSTERED (ID),
CONSTRAINT UCI_MyClusteredIndexName UNIQUE CLUSTERED (UserID ASC,EntryDate ASC,ID ASC)
);
In order to change a tables clustered index, the clusteredd index must be dropped, which converts the table into a heap and then the new clustered index is applied. Because Azure does not support heaps (tables without clustered indexes) it is not possible to change the clustered index without dropping the table and recreating it. In Azure you can not specify a clustered index in any other place other than the table create statement.