SQL 如何将单租户数据库转换为多租户数据库

2 sql-server-2008 database-design sql-server sql-server-2012 sql-server-2016

我们有一个遗留应用程序。出于某种原因,有人决定为每个客户创建一个数据库。所以我们有 500 多个具有相同架构的数据库。

我想转换成一个多客户端数据库。原始客户数据库没有 CustomerId。新的单一数据库确实如此。

我们正在考虑用视图替换旧的数据库,因此旧的应用程序插入/更新仍然可以工作。

老客户数据库:

CREATE TABLE [CustomerOne].[dbo].[CustomerTransaction]
(
    [CustomerTransactionid] [int] identity(1,1) primary key NOT NULL,
    [QuantityBought] [int] NULL,
)
Run Code Online (Sandbox Code Playgroud)

新报告数据库具有 CustomerId:

CREATE TABLE ReportingDB.[dbo].[CustomerTransaction]
(
    [CustomerTransactionid] [int] identity(1,1) primary key NOT NULL,
    [Customerid] [int] NOT NULL,
    [QuantityBought] [int] NULL,
)
Run Code Online (Sandbox Code Playgroud)

用视图替换旧数据库

create view [CustomerOne].[dbo].[CustomerTransaction]
as
select 
    [CustomerTransactionid]
    ,1 as [CustomerId]
    ,[quantitybought]
from ReportingDB.[dbo].[CustomerTransaction]
where Customerid = 1
with check option
Run Code Online (Sandbox Code Playgroud)

这失败:

-- Attempt inserting into View
insert into CustomerOne.dbo.Customertransaction 
(Quantitybought)
values (4)

Msg 515, Level 16, State 2, Line 1
Cannot insert the value NULL into column 'customerid', table 'ReportingDB.dbo.customertransaction'; column does not allow nulls. INSERT fails.
The statement has been terminated.
Run Code Online (Sandbox Code Playgroud)

如何在没有触发器的情况下使最终语句成功?否则我们将不得不使用某种触发器来让它工作。也许没有办法轻松获得统一的数据库。

我们试图避免更改旧的应用程序代码(插入和更新语句)。

Pau*_*ite 5

这可以使用SESSION_CONTEXT(SQL Server 2016 中的新功能)来实现。在以前的版本中CONTEXT_INFO可以使用,但它不太健壮(不能是只读的,每个会话只有一个插槽)。

例子

首先,DEFAULT在基础表的CustomerId列上设置 a :

CREATE TABLE ReportingDB.[dbo].[CustomerTransaction]
(
    [CustomerTransactionid] [int] identity(1,1) primary key NOT NULL,
    [Customerid] [int] NOT NULL
        DEFAULT TRY_CONVERT(integer, SESSION_CONTEXT(N'CustomerID')),
    [QuantityBought] [int] NULL,
);
Run Code Online (Sandbox Code Playgroud)

然后定义视图如下:

CREATE OR ALTER VIEW dbo.CustomerOneTransaction
AS
    SELECT
        CT.CustomerTransactionid,
        CT.Customerid,
        CT.QuantityBought
    FROM ReportingDB.dbo.CustomerTransaction AS CT
    WHERE
        CT.Customerid = 1
        AND TRY_CONVERT(integer, SESSION_CONTEXT(N'CustomerID')) = 1
    WITH CHECK OPTION;
Run Code Online (Sandbox Code Playgroud)

每个会议将需要设置客户ID一次使用sys.sp_set_session_context

EXECUTE sys.sp_set_session_context
    @key = N'CustomerID',
    @value = 1;
Run Code Online (Sandbox Code Playgroud)

如果密钥在连接的生命周期内不可变(通常为 true),请使用以下@read_only选项:

EXECUTE sys.sp_set_session_context
    @key = N'CustomerID',
    @value = 1,
    @read_only = 1;
Run Code Online (Sandbox Code Playgroud)

这种安排允许您的插入查询工作,并确保CustomerID在使用视图插入或更新时始终匹配会话键,否则CHECK_OPTION返回错误。对于删除,非法操作将被忽略而不是引发错误。

这个答案解决了提出的问题,但实际上,出于安全性、可管理性和可用性的原因,我几乎总是更喜欢每个客户的数据库。

演示: db<>小提琴

附加信息

您还可以将此模式与行级安全性结合使用,以在没有视图的情况下强制执行客户隔离。每个客户的数据库是软件即服务公司(不是银行和金融公司 - 客户数据库隔离的好处对许多其他行业没有真正意义)最常用的模式。