Обновляемые разделенные представления для многопользовательской базы данных - PullRequest
1 голос
/ 18 апреля 2019

Использование 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

У нас также есть анонимные пользователи, которые, имея свои собственные предпочтения сеанса, будут обмениваться общими данными - продуктами, ценами и т. Д. Сеансы для этих пользователей настраиваются путем создания представления, которое указывает на анонимные данные сеанса и переопределяет их идентификатор сеанса. .

/* 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

Проблема, с которой мы столкнулись, заключается в том, что мы больше не можем изменять данные в представлении, поскольку к одной из таблиц применяется константа, примененная к одному из полей (т.е. мы переопределяем идентификатор сеанса anon новым идентификатором сеанса)

/* 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 и выполнение намного медленнее, чем хотелось бы. Особенно, когда есть много сессий.

Пожалуйста, обратите внимание, что приведенные выше данные не указывают на наши таблицы в реальном времени, а только на используемые принципы. У нас есть тысячи одновременных сеансов и таблиц данных с десятками тысяч строк, поэтому эффективность так же важна, как и простота поддержки.

...