SQL Server 组成部分 用户权限控制 底层查询原理

一、用户管理

1.用户创建

create login 'nhy_HL' with password='2009!!)$qazWSX'

use api_tosys_in
create user 'aa' for login 'aa' with default_schema=dbo
exec sp_addrolemember 'db_owner', 'aa'

use express
create user 'aa' for login 'aa' with default_schema=dbo
exec sp_addrolemember 'db_owner', 'aa'

2.孤立用户

SQL Server在搬迁的会出现很多孤立用户,微软没有自动的处理。
因为我们的数据库权限表都不会在应用数据库中,但是每次对数据库作迁移的时候,单个数据库却带着它的数据库用户对象。
并且我们在新的数据库机器上也不能登录这些账号,但是它却静悄悄的存在我们的数据库中。
后续版本SQL Server将删除该功能。请避免在新的开发工作中使用该功能,请改用 ALTER USER。

# 列出当前数据库中的孤立用戶*/
exec  sp_change_users_login 'report'

# 关联孤立用户
use 数据库名
exec sp_change_users_login 'UPDATE_ONE','用戶名','登录名'

# 更改架构权限
ALTER AUTHORIZATION ON SCHEMA::db_owner TO dbo;

# 如果已有登录用戶,将用戶名映射为指定的登录名
use 数据库名
exec sp_change_users_login 'AUTO_FIX','用戶名'


二、权限管理

1. 操作步骤

  • 1.首先进入数据库级别的【安全性】-【登录名】-【新建登录名】

  • 2.在【常规】选项卡中,如下图所示,创建登陆名.

  • 3.在【用户映射】选项卡中,如下图所示,勾选需要设置的数据库,可以并设置【架构】(不设置架构确认后会默认使用dbo),点击【确认】按钮,完成创建用户的操作

  • 4.现在可以对数据库中的表进行权限的设置了,【表】->【 属性】,在【权限】选项卡中,如下图所示,依此点击【添加】-【浏览】-【选择对象】

  • 5.点击【确认】后,可以下面的列表中找到对应的权限,如果还想细化到列的权限的话,右下角还有一个【列权限】的按钮可以进行设置,点击【确认】

2. 注意事项

  • 1.在上面的第3步骤中需要注意:如果这里没有选择对应的数据库的话,之后去数据库中是找不到User的。

  • 2.在上面的第3步骤,选择数据库后,需要点击【确认】按钮,完成创建用户操作,如果这个时候直接去设置【安全对象】,是无法在【添加】-【特定对象】-【对象类型】-【登陆名】-【浏览】中找到刚刚新建的用户的。

  • 3.在第6步的【显式权限】列表中,如果选择了【Control】这个选项,那么在【Select】中设置查询【列权限】就没有意义了,查询就不会受限制了。如果设置【列权限】,在正常情况下会显示报错信息

  • 4.在数据库的【安全性】-【TestUser】-【属性】-【安全对象】-【添加】-【对象类型】这里有更多关于数据库级别的一些对象类型可以设置。

3. 代码授权

--方法一, 创建登陆帐户(create login)登录名为dba,设置密码为123,默认数据库为abcd
create login dba with password='123', default_database=abcd

--方法二, 添加登录名为test,设置密码为123,默认数据库为abcd  
EXEC sp_addlogin 'test','123','abcd'

--为登陆账户创建数据库用户(create user),在mydb数据库中的security中的user下可以找到新创建的dba
create user dba1 for login dba with default_schema=dbo
结果见下图A

--给用户赋予角色(权限),赋予数据库用户“db_owner”权限
exec sp_addrolemember 'db_owner', 'dba'

--创建bbb登录名, 创建数据库用户“dba1”“dba2”访问多个数据库并给与权限
create login bbb with password='123'

use aaa
create user dba1 for login bbb with default_schema=dbo
exec sp_addrolemember 'db_owner', 'dba1'

use bbb
create user dba2 for login bbb with default_schema=dbo
exec sp_addrolemember 'db_owner', 'dba2'

结果见下图

--创建角色 r_test  
EXEC sp_addrole 'r_test'  

--添加登录名 l_test,设置密码为a@cd123,默认数据库为abcd  
EXEC sp_addlogin 'l_test','a@cd123','abcd'  

--为登录名 l_test 在数据库中添加用户和架构 u_test(不太用,一般用默认dbo做架构)
EXEC sp_grantdbaccess 'l_test','u_test'  

--添加 u_test 为角色 r_test 的成员  
EXEC sp_addrolemember 'r_test','u_test'  
--查看数据库的owner
EXEC sys.sp_helpdb
--禁用登陆帐户
alter login dba disable
--启用登陆帐户
alter login dba enable
--登陆帐户改名
alter login dba with name=dba_tom
--登陆帐户改密码: 
alter login dba with password='aabb@ccdd'
--数据库用户改名: 
alter user dba with name=dba_tom
--更改数据库用户 defult_schema: 
alter user dba with default_schema=sales
--删除数据库用户: 
drop user dba
--删除 SQL Server登陆帐户: 
drop login dba

-- 允许查询数据权限
GRANT SELECT  ON [dbo].[表名] TO [用户]  
-- 允许更新数据权限
GRANT UPDATE  ON [dbo].[表名] TO [用户]
-- 允许插入数据权限
GRANT INSERT  ON [dbo].[表名] TO [用户]
-- 允许删除数据权限
GRANT DELETE  ON [dbo].[表名] TO [用户]

--拒绝用户删除权限
DENY DELETE  ON [dbo].[表名] TO [用户]

--取消用户删除权限
REVOKE DELETE  ON [dbo].[表名] TO [用户]


- deny 和 revoke 的区别

4. 权限的元素

  • 角色
    角色是一类权限的组合。

    注意:不要将用户创建的数据库角色添加到固定的服务器数据库角色当中去,否则将导致固定的数据库角色的权限升级。

  • 服务器角色的拥有者只有登入名,服务器角色是固定的,用户无法创建服务器角色。

    注意:一般不建议给用户直接分配服务器角色,因为服务器角色是全局的,也就是说你拥有了服务器级别的权限,一般建议给用户分配数据库,然后给对应的数据库分配数据库角色权限。

  • 用户
    用户是数据库级的概念,数据库用户必须绑定具体的登入名,你也可以在新建登入名的时候绑定此登入名拥有的数据库,当你绑定登入名数据库后,数据库默认就创建了此登入名同名的数据库用户,登入名与数据库用户之间就存在关联关系,数据库用户是架构和数据库角色的拥有者,即你可以将某个架构分配给用户那么该用户就拥有了该架构所包含的对象,你也可以将某个数据库角色分配给用户,此用户就拥有该数据库角色的权限。

  • 架构
    架构是对象的拥有者,架构本身无权限,架构包含数据库对象:如表、视图、存储过程和函数等,平时最常见的默认架构dbo.,如果没指定架构默认创建数据库对象都是以dbo.开头,架构的拥有者是数据库用户、数据库角色、应用程序角色。用户创建的架构和角色只能作用于当前库。


一、SQL Server组成部分

1. 关系引擎

  • 主要作用是优化和执行查询

包含三大组件:

(1)命令解析器:检查语法和转换查询树。

(2)查询执行器:优化查询。

(3)查询优化器:负责执行查询。

2. 存储引擎

  • 管理所有数据及涉及的IO

包含三大组件:

(1)事务管理器:通过锁来管理数据及维持事务的ACID属性。

(2)数据访问方法:处理对行、索引、页、行版本、空间分配等的I/O请求。

(3)缓冲区管理器:管理SQL Server的主要内存消耗组件Buffer Pool。

3. Buffer Pool

  • 包含SQL Server的所有缓存。如计划缓存和数据缓存。

4. 事务日志

  • 记录事务的所有更改。保证事务ACID属性的重要组件。

5. 数据文件

  • 数据库的物理存储文件。

6. SQL Server网络接口

  • 建立在客户端和服务器之间的网络连接的协议层

四、查询的底层原理

1. 查询的底层过程

1.当客户端执行一条T-SQL语句给SQL Server服务器时,会首先到达服务器的网络接口,网络接口和客户端之间有协议层。

2.客户端和网络接口之间建立连接。使用称为“表格格式数据流”(TDS) 数据包的 Microsoft 通信格式来格式化通信数据。

3.客户端发送TDS包给协议层。协议层接收到TDS包后,解压并分析包里面包含了什么请求。

4.命令解析器解析T-SQL语句。命令解析器会做下面几件事情:

(1)检查语法。发现有语法错误就返回给客户端。下面的步骤不执行。

(2)检查缓冲池(Buffer Pool)中是否存在一个对应该T-SQL语句的执行计划缓存。

(3)如果找到已缓存的执行计划,就从执行计划缓存中直接读取,并传输给查询执行器执行。

(4)如果未找到执行计划缓存,则在查询执行器中进行优化并产生执行计划,存放到Buffer Pool中。

5.查询优化器优化SQL语句

当Buffer Pool中没有该SQL语句的执行计划时,就需要将SQL传到查询优化器,通过一定的算法,分析SQL语句,产生一个或多个候选执行计划。选出开销最小的计划作为最终执行计划。然后将执行计划传给查询执行器。

6.查询执行器执行查询

查询执行器把执行计划通过OLE DB接口传给存储引擎的数据访问方法。

7.数据访问方法生成执行代码

数据访问方法将执行计划生成SQL Server可操作数据的代码,不会实际执行这些代码,传送给缓冲区管理器来执行。

8.缓冲区管理器读取数据。

先在缓冲池的数据缓存中检查是否存在这些数据,如果存在,就把结果返回给存储引擎的数据访问方法;如果不存在,则从磁盘(数据文件)中读出数据并放入数据缓存中,然后将读出的数据返回给存储引擎的数据访问方法。

9.对于读取数据,将会申请共享锁,事务管理器分配共享锁给读操作。

10.存储引擎的数据访问方法将查询到的结果返回关系引擎的查询执行器。

11.查询执行器将结果返回给协议层。

12.协议层将数据封装成TDS包,然后协议层将TDS包传给客户端。

2. 查询执行顺序

本文所涉及的查询语句如下,主要是citizen表与city表进行join,然后筛掉city_name != "上海"的数据,接着按照city_name进行分组,统计每个城市总人数大于2的城市,具体如下:

查询语句

SELECT
 city.city_name AS "City",
 COUNT(*) AS "citizen_cnt"
FROM citizen
 JOIN city ON citizen.city_id = city.city_id
WHERE city.city_name != '上海'
GROUP BY city.city_name
HAVING COUNT(*) >= 2
ORDER BY city.city_name ASC
LIMIT 2

执行步骤

上面SQL查询语句的书写书序是:

SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ...

但是执行顺序并不是这样,具体的执行顺序如下步骤所示:

  • 1.获取数据 ( From, Join )
  • 2.过滤数据 ( Where )
  • 3.分组 ( Group by )
  • 4.分组过滤 ( Having )
  • 5.返回查询字段 ( Select )
  • 6.排序与分页 ( Order by & Limit / Offset )

提示:本文旨在说明通用的SQL执行底层原理,对于其优化技术不做考虑,比如谓词下推、投影下推等等。

执行的底层原理

其实上面所说的SQL执行顺序就是所谓的底层原理,当我们在执行SELECT语句时,每个步骤都会产生一张 虚拟表(virtual table) ,在执行下一步骤时,会将该虚拟表作为输入。指的注意的是,这些过程是对用户透明的。

你可以注意到,SELECT 是先从FROM 这一步开始执行的。在这个阶段,如果是多张表进行JOIN,还会经历下面的几个步骤:

获取数据 ( From, Join )

  • 首先会通过 CROSS JOIN 求笛卡尔积,相当于得到虚拟表 vt1-1;
  • 接着通过ON 条件进行筛选,虚拟表 vt1-1 作为输入,输出虚拟表 vt1-2;
  • 添加外部行。我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3

过滤数据 ( Where )

经过上面的步骤,我们得到了一张最终的虚拟表vt1,在此表之上作用where过滤,通过筛选条件过滤掉不满足条件的数据,从而得到虚拟表vt2。

分组 ( Group by )

经过where过滤操作之后,得到vt2。接下来进行GROUP BY操作,得到中间的虚拟表vt3。

分组过滤 ( Having )

在虚拟表vt3的基础之上,使用having过滤掉不满足条件的聚合数据,得到vt4。

返回查询字段 ( Select )

当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 和 DISTINCT 阶段。首先在 SELECT 阶段会提取目标字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表 vt5-1 和 vt5-2。

排序与分页 ( Order by & Limit / Offset )

当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY 阶段,得到虚拟表 vt6。最后在 vt6 的基础上,取出指定行的记录,也就是 LIMIT 阶段,得到最终的结果,对应的是虚拟表 vt7

SQL语句被可靠无误的发送到了服务器端,SQLSERVER引擎中第一个模块就来接待这个SQL数据。这个模块的名字叫:Open Data Services。它监听新的连接;清除失败连接;将结果集、消息和状态返回给客户端。

SQLSERVER客户端和服务器端之间传输数据,数据包是有格式的。在SQLSERVER中被称为tabular data stream。这个数据流是令牌控制客户端和服务器端对话(否则,客户端说了N句话,服务器端返回N句话,没有令牌就混在一起了,不知道哪个回答是对应哪个请求的)。我们往往不能直接和Open Data Services打交道,把数据放进来。而是我们必须通过ODBC、ADO或DB-Library来发送tabular data stream。而SQLSERVER返回的数据结果,也是通过这些ODBC之类发回tabular data stream。你看看SQLSERVER设计的多巧妙,一个通用数据访问接口屏蔽了你和SQLSERVER之间,就如同WINDOWS API屏蔽了内核让你无法访问,就如同DirectX屏蔽了UI和外设的操控。

SQL语句-ODBC-编码成tabular data stream-IPC或RPC-网络协议-IPC或RPC-解码tabular data stream-ODBC-Open Data Services。

Open Data Services监测客户端连接。如果并发太多,它会创建连接,如果服务完,它会自己维护连接归入池中。在池中保留一段生命期,它会自己释放连接。如果有的客户端连接中途突然断掉(如客户端重启了),它在侦听后无回应,它也会自己整理自己的连接的。我们在SQLSERVER线程中看到的连接,就是Open Data Services创建的。

Open Data Services有了连接(可能是创建的可能是从池里拿出来的,池化、创建、销毁都是非常讲究技能的。池化多少,上下文资源如何保留,池化多长时间,什么时候该销毁,调度不当就会严重消耗资源),就把SQL接住。这时,是接到了Open Data Services的读缓冲区里面。这个缓冲区为高性能处理数据的SQLSERVER带来一丝喘息机会,而就这一丝喘息机会,让SQLSERVER可以游刃有余(你的设计有吗?)。而Open Data Services有一个写缓冲区。SQLSERVER把检索到的数据,检索出来就立即放进写缓冲区,写缓冲区一满就立即被Open Data Service发走。当我过去研究SQLSERVER原理的时候,我常常赞叹,一个小小的SQLSERVER外围模块都设计如此精妙,实在让人佩服。我们经常在追求海量数据存储和Cache架构,我们却无视我们手边的SQLSERVER。

SQL语句放到读缓冲区,SQLSERVER的关系引擎就开始工作了。它总是在侦听这个读缓冲区。

SQL语句遇到的关系引擎的第一个模块就是命令分析器。我们在SQL查询分析器中看到的查询分析结果就是它的输出杰作。它来构造查询树。首先是将你的SQL语句规范化(你想想你写的软件代码,输入数据来了什么都不管就直接处理,连输入数据校验都没有,怎能稳定),否则以后的步骤将不好操作,如果你的SQL语句有语法错误,这个查询树的构造就无法完成,于是中断。而要规范一个SQL语句,首先要从SQL语法库中抽取SQLSERVER现有支持的各种语法和函数。

一旦构造成功,关系引擎的第二个模块就是命令优化器,来裁剪这棵树。一个SQL语句可以生成多种执行和优化的方案(如果你使用过那种SQL优化工具的话,你就能理解),SQLSERVER会选择最节省内存、CPU利用率、I/O次数(I/O是性能优化最要命的地方,往往性能就瓶颈在I/O上)的那一种方案。优化器会根据每张表的数据统计(有时候你为了性能优化,必须定时期同步更新一下统计,否则优化就会有误差)。而且优化器也会根据查询树去选择合适的索引(如果使用索引代价大,它会自动选择全表扫描),优化器也会根据查询树知道先取哪些表的数据,然后再内存中如何合并数据,以得到你想要的结果(有时候想想优化器真伟大,你一个SQL过去,它需要在极短的时间内做多少事啊,为了能在极短时间内确定一个相对优化的方案,它也不可能穷举所有可能的方案,所以我们做海量数据优化的时候,往往评估多种方案,然后修改自己的SQL语句以符合产生最优的方案)。


规范化、优化完SQL语句,就要产生执行计划了。SQL管理器负责执行计划的产生。因为你发过来的SQL语句可能是一个SELECT,也可能是一个INSERT或UPDATE。即使SELECT,也面临着用户权限的限制(你如果设置过某一个SQLSERVER用户的对象权限和列权限,你就会明白)。而INSERT之类更新语句,又会涉及到权限、默认值、约束、表达式、主外键、触发器。一个优化完的SQL,具体要真正让SQLSERVER从内存或硬盘上把数据找出来或者更新回去,需要很多细节的步骤。

查询执行器来负责SQL的执行。因为SQL的执行要涉及到事务、锁、等待、CPU调度,内存页失效影响、I/O存取影响,所以查询执行器会协调很多其他模块,但各个模块来负责处理,而查询执行器并不真正全部包办,否则让事务管理器、锁管理器、索引管理器、页面文件管理器、缓冲管理器、行管理器、日志管理器干吗去。


查询执行器是查询引擎的最后一个模块,接下来的模块都属于存储引擎的范畴。所以,从上看,查询引擎最主要是构造SQL查询树、优化裁剪SQL查询树,根据查询树产生执行计划,然后协调执行查询树,把结果返回去。

而真正要把数据取出来或存进去,就需要存储引擎来工作了。

首先根据执行计划,要存取哪些数据页和索引页。这就是访问方法管理器(access methods manager)要做的事情。但其实真要打开这些页,还不是访问方法管理器自己要亲手干的。

亲手干这个活的是一个叫“缓冲区管理器”的模块。因为在硬盘上的数据是不可能计算处理的,必须要在内存中才能让CPU来计算。所以要存取那些数据页和索引页,就通知让缓冲区管理器来做。如果数据没有在内存中,就让缓冲区管理器来读入,如果数据已经在内存中了,缓冲区管理器只有返回即可。这个过程是被缓冲区管理器来屏蔽的,对于访问方法管理器是透明的。大家可不要以为访问方法管理器啥事不做,只是一个发布调度命令的。这可错怪了它。因为SQLSERVER要保证高速处理,必须预先预测好哪些数据页和索引页要处理。不能人家缓冲管理器已经处理完,你访问方法管理器才计算下一步将要处理的页面。要知道,这些管理器可是不分哪个用户来处理的。如果接受来自100多个并发的用户,发来各种各样的数据处理请求,你怎么能预测到哪些数据页和索引页要处理呢?这就需要一个统一的调度。而且这个统一的调度也影响着缓冲区管理器。你不能请求一个大数据,缓冲区管理器这才火烧屁股才扩大缓冲区,然后装载数据,那样流水线就停下了。缓冲区管理器必须预先知道将在不久要有一个大数据,所以在并行运算的时候就有独立线程来扩展了缓冲区。因为扩大缓冲区还和操作系统有关。你要扩大缓冲区,正好遇到WINDOWS页面失效,就涉及到你的虚拟文件的变化。而页面失效又会影响CPU和I/O。所以页面失效是一个性能影响很大的问题。而提高命中率是我们性能优化一直努力的重点。如果数据长时间不用,缓冲区管理器就要让这块内存数据过期,可以被新的数据覆盖。否则缓冲区老加载不卸载也不行。再说,有些数据已经被更新了,你数据老化了,不重新读入,你的数据就引起读错误了。

我们知道,数据页包含数据行。索引页包含索引行。数据行就由行管理器来控制。而索引行,由索引管理器来负责。

而单行上的检索、修改、执行,又被事务管理器和锁管理器影响着。事务,有显性事务和隐性事务两种。而锁,又有共享锁、排它锁、更新锁、意向锁。而锁,还分为行锁、页锁、表锁、数据库锁。而锁,又有死锁的可能性。锁的不同,加上事务的影响,这个行是否能读、能修改,能怎样的读(读一致还是脏读),是等待事务和锁,还是可以进行,就受了很多影响。因为一张数据页上放的行是有限的,尤其还有填充度的影响(如填充度为80%,就这个数据页面只能填充80%就必须分页,以防以后有数据插入的时候,就非常影响数据插页,这也是性能影响比较大,尤其在插入数据比较多的情况下)。SQLSERVER的一张数据页默认是64K,除去填充度和数据头,也没有多少可存储的数据了。这就是为了关系型数据库都劝阻大家要小表大数据。也就是说,列要少,列要短,频繁访问的列要在前。数据可以海量。如果行长了,你想要检索和更新多少数据页,这需要多少页面调度,面临着页面失效和锁机制的影响。而且,大文本和可变行,都是指针存储,需要跳转查找,更浪费了不少时间。

而索引管理器,最主要在维护着索引B树。没有索引页,我们就要做全表扫描了,那需要载入多少数据页,而且还要逐行扫描,如果遇上事务和更新锁,就更有问题。所以,索引是非常重要的。而一个表,可以建立很多索引。索引,能直接找到所需要的行,而无须全表扫描。但是,你的索引如果仅仅是男女,或者你的索引涉及到可变行,都对索引不利。索引,不宜建立多。否则维护索引页的成本和消耗也非常多。索引页更要涉及到插页、拆页,频繁改动涉及到索引的字段,会让索引页剧烈变动,尤其数据量越大影响越大。我就不在这里讲解如何利用索引优化SQL了,否则一本书也讲不完。

数据不断存取,数据不断被维护,载入内存或从内存中写入硬盘。其实都是惰性写入器在照顾。惰性写入器来定期扫描老化数据,让硬盘和内存中的数据是一致的。有这个惰性写入器,就有了内存和硬盘的差异时间窗。就有可能出现异常。一旦服务器突然断电,没有来得及写会磁盘的怎么办。也也涉及到另一个模块:日志管理器。日志管理器利用检查点的机制维护着日志文件。在服务器重新启动的时候,重写载入日志来把数据恢复到一致性。写日志,当然要比写数据要容易的多,快的多。因为写数据要操控内存和硬盘,还要注意权限、锁、事务,所以突然断电,你还没反应就来不及了。所以日志这种轻量级的方法,就可以在恢复一致性上有很好的帮助(当然,也丢失数据。日志页也没来得及写入硬盘)。

讲到这里,就剩下事务管理器、锁管理器。这两个管理器和显性事务、隐性事务、显性锁、隐性锁、事务隔离级别、锁级别、行管理器、索引管理器都有很多关系。微软有WINDOWS优势,又有Jim Gray这样的巨师坐镇(Jim Gray是图灵奖获得者,就是此爷提出了数据库事务这一概念。盖茨为了让此爷为微软工作,而此爷不喜欢雷德蒙天天下雨的天气,于是在加州阳光中给此爷单独建了一座研究院)。所以,在性能上,我个人认为SQLSERVER的性能是非常优秀的(你想想,一个数据库产品的性能受什么方面的影响)。至于业界老称SQLSERVER无法管理海量数据,性能不佳,我个人感觉都是业界在以讹传讹。而尤其中国内地IT业界,大部分都是入门级在跟帖嘈杂,尤其还有一批更不懂技术的媒体记者或写手。

如果真要去说SQLSERVER不行,大型海量数据管理必须用某某数据库产品,我建议从内部原理、内部架构、内部实现三个层次诸多方面来剖析到底在不在理。

最后就是I/O管理器了。我一直不认同SQLSERVER内核中有I/O管理器。因为SQLSERVER使用的是和WINDOWS同样的页面调度和页面分配方法。何必要自己另创一套呢。就如同SQLSERVER把页面、硬盘、内存、线程、CPU交给了WINDOWS一样。SQLSERVER作为WINDOWS上的一个应用软件,应该和WINDOWS上的其他软件一样被WINDOWS管理。SQLSERVER又不跨平台,无须自己管理。

除了SQLSERVER这些内核涉及精妙以外,SQLSERVER的外围工具也设计的相当好。如SQLSERVER的用户安全性管理方法、对象分类(表、列、约束、默认、索引、触发器、存储过程、视图、主键)、对象权限方法、元数据自管理方法、SQL语言、SQL查询分析器、SQL跟踪器、SQL性能分析器、SQL数据库(master\msdb\tempdb\model)。
Copyright © 2009 - Now . XPBag.com . All rights Reserved.
夜心的小站 » SQL Server 组成部分 用户权限控制 底层查询原理

提供最优质的资源集合

立即查看 了解详情