多租户数据库的可更新分区视图

时间:2019-04-18 10:41:20

标签: sql sql-server view multi-tenant

使用SQL Server 2016 +

我们目前有一个多租户数据库,每个用户都分配有自己的架构。我们正在使用分区视图来简化访问此数据的过程。我们具有适当的约束条件,这些条件使我们可以通过视图更新基表。下面是一个简化版本,仅使用单个会话表。

DROP VIEW IF EXISTS dbo.SESSIONS;
DROP VIEW IF EXISTS dbo.SESSIONS_readonly;
DROP VIEW IF EXISTS dbo.SESSIONS_editable;
DROP VIEW IF EXISTS dbo.Session3;
DROP TABLE IF EXISTS dbo.SessionAnon;
DROP TABLE IF EXISTS dbo.Session1;
DROP TABLE IF EXISTS dbo.Session2;

/* Create tables, primary keys and constraints required for partioned views */
/* Sessions 1 & 2 would be the equivalent of "logged in" users - i.e. they have all their own data */
/* Session Anon is a dummy session that provides a template of data for any "not logged in" users */
CREATE TABLE dbo.SessionAnon (Session INT           NOT NULL
                                  DEFAULT 0
                                  CONSTRAINT ck_sessionAnon_ID CHECK (Session = 0)
,                             Name    NVARCHAR(255) NOT NULL
,                             Val     INT           NOT NULL
,                             CONSTRAINT PK_SessionANON
                                  PRIMARY KEY (Session, Name));

CREATE TABLE dbo.Session1 (Session INT           NOT NULL
                               DEFAULT 1
                               CONSTRAINT ck_session1_ID CHECK (Session = 1)
,                          Name    NVARCHAR(255) NOT NULL
,                          Val     INT           NOT NULL
,                          CONSTRAINT PK_Session1
                               PRIMARY KEY (Session, Name));

CREATE TABLE dbo.Session2 (Session INT           NOT NULL
                               DEFAULT 2
                               CONSTRAINT ck_session2_ID CHECK (Session = 2)
,                          Name    NVARCHAR(255) NOT NULL
,                          Val     INT           NOT NULL
,                          CONSTRAINT PK_Session2
                               PRIMARY KEY (Session, Name));
GO

/* Create partitioned view */
CREATE VIEW dbo.SESSIONS
AS
    SELECT * FROM dbo.SessionAnon UNION ALL 
    SELECT * FROM dbo.Session1 UNION ALL 
    SELECT * FROM dbo.Session2; 
GO

/* Insert data into sub tables via view */
INSERT INTO dbo.SESSIONS (Session, Name, Val)
VALUES (0, 'Children', 2)
,      (0, 'Parents', 2)
,      (0, 'GrandParents', 4)
,      (1, 'Children', 1)
,      (1, 'Parents', 2)
,      (2, 'Children', 3)
,      (2, 'Parents', 1)
,      (2, 'GrandParents', 2);

SELECT * FROM dbo.SESSIONS;
GO

我们还有匿名用户,这些用户在拥有自己的会话首选项时会共享常见数据-产品,价格等。这些用户的会话是通过创建一个指向匿名会话数据并覆盖其会话ID的视图来配置的

/* Session 3 is an anonymous user with a view created that points to the anon session with its own session ID rather than a seperate table with its own data */
/* This is done as the real data sets here a quite large and there is less overhead creating a view than copying the table. */

CREATE VIEW dbo.Session3
AS
    SELECT  3 [Session] , Name  , Val
      FROM  dbo.SessionAnon
GO

/* Add Session 3 to the view */

DROP VIEW dbo.SESSIONS;
GO

CREATE VIEW dbo.SESSIONS
AS
    SELECT * FROM dbo.SessionAnon UNION ALL 
    SELECT * FROM dbo.Session1 UNION ALL 
    SELECT * FROM dbo.Session2 UNION ALL 
    SELECT * FROM dbo.Session3;
GO

SELECT * FROM dbo.Sessions

GO

我们面临的问题是,我们不能再更改视图中的数据,因为其中一个表对一个字段应用了常量(即,我们用新的会话ID覆盖了匿名会话ID)

/* This will now fail as one of the tables in the view has a constant field */
INSERT INTO dbo.SESSIONS (Session, Name, Val)
VALUES (1, 'GrandParents', 1);

GO

我们目前采用的解决方案包括创建一个可编辑视图(包含已登录的用户)和一个可读视图(包含可编辑用户和匿名用户)

/* Create two views, one editable (contains logged in sessions only */
/* and one read-only which reads from eduitable and adds anon sessions */
DROP VIEW IF EXISTS dbo.SESSIONS_readonly;
DROP VIEW IF EXISTS dbo.SESSIONS_editable;
GO

CREATE VIEW dbo.SESSIONS_editable
AS
    SELECT * FROM dbo.SessionAnon UNION ALL 
    SELECT * FROM dbo.Session1 UNION ALL 
    SELECT * FROM dbo.Session2
GO

CREATE VIEW dbo.SESSIONS_readonly
AS
    SELECT * FROM dbo.SESSIONS_editable UNION ALL
    SELECT * FROM dbo.Session3
GO

INSERT INTO dbo.SESSIONS_editable (Session, Name, Val)
VALUES (1, 'GrandParents', 1);

INSERT INTO dbo.Session1 (Session, Name, Val)
VALUES ( 1 , N'Great Grand Parents' , 0 )

SELECT * FROM dbo.SESSIONS_readonly

这可以正常运行,执行计划可以“解决”很多复杂性。统计数据表明,这两个查询足够接近,不必担心。但是,这确实要求我们为每个表维护两组视图。如果不是这种情况,那将是更可取的-即一种视图可以覆盖全部视图。

有人知道有关此方面的可用解决方案吗?我们尝试使用“而不是”触发器来允许写入,但是动态sql的生成和执行比我们想要的要慢得多。尤其是在会话很多的时候。

请注意,以上数据并不表示我们的活动表仅是所使用的原则。我们有成千上万的同时会话和具有成千上万行的数据表,因此效率与支持的便捷性同样重要。

0 个答案:

没有答案