代码改变世界

《Microsoft SQL Server企业级平台管理实践》笔记

2014-12-05 16:47  杰克小薛  阅读(905)  评论(0编辑  收藏  举报

 - 页是 SQL Server 中数据存储的基本单位,大小为 8KB。

- 区是空间管理的基本单位,8个物理上连续的页的集合(64KB)。

- 页的类型包括:

    1. Data

    2. Index

    3. Text/Image

    4. Global Allocation Map

    5. Shared Global Allocation Map

    6. Page Free Space, Index Allocation Map

    7. Bulk Changed Map

    8. Differential Changed Map

- 在一个数据库里,绝大多数的页面都是 Data 或者 Text/Image 类型。

- varchar(max), nvarchar(max) 可以突破 8KB 大小的限制。

    1. 如果字段行的总长不超过 8KB,数据还是会一起存放在普通的数据页里。

    2. 如果超过 8KBSQL Server 就会把这些字段的数据分开,单独存放在一种叫 Row-Overflow(行溢出)的页面里。

- 区分为:统一区和混合区。统一区由单个对象所有,混合区最多可由8个对象共享。

- 通常从混合区向新表或索引分配页。当表或索引增长到8页时,将变成使用统一区进行后续分配。

- SQL Server 数据库引擎内部会将日志文件分成多个虚拟日志单元。日志文件每自动增长一次,会至少增加一个虚拟日志单元。

- tempdb 数据库是一个全局资源,主要保存这三类对象:

    1. 用户对象:用户自定义表和索引,系统表和索引,全局临时表和索引,局部临时表和索引,表变量,表值函数中返回的表。

    2. 内部对象:SQL Server 数据库引擎创建的,用户处理SQL语句。包括用于游标操作的工作表,用户哈希联接或哈希聚合操作的工作表,GROUP BY, ORDER BY 或 UNION 查询的中间排序结果。

    3. 版本存储区:版本存储区是数据页的集合,它包含支持使用行版本控制的功能所需的数据行。

- SQL Server 三种组织分区中的数据和索引页的方法:

    1. B树存储有聚集索引的表数据页(表数据和聚集索引的排序顺序一致)

    2. 堆是没有聚集索引的表(SQL Server 对堆的管理比较简单,算法能力弱,所有大的,经常使用的表都应该建立聚集索引)

    3. 非聚集索引(表数据和非聚集索引的排序顺序不一致)

- DELETE vs TRUNCATE

    1. 数据表上建立聚集索引(否则 DELETE 语句后,表仍会包含空页)。

    2. 如果删除整张表数据,使用 TRUNCATE TABLE(日志少,锁少,释放空间)。

    3. 如果删除表本身,使用 DROP TABLE

- DBCC SHRINKFILE 做的都是区级别的动作,它会把使用过的区前移,把没有使用的区从文件中移除。

    1. 如果一个数据库中有很多只使用了一两个页面的区,DBCC SHRINKFILE 的效果会不明显。

    2. 解决办法:重建聚集索引。

    3. 如果是 Text/Image 数据类型,使用 DBCC EXTENTINFO 分析数据文件里所有区的分配情况,考虑重建对象。

- SQL Server 会为所有的修改记录日志,以便将来重新提交或者回滚。(机器断电,SQL Server 服务崩溃)

- DBCC LOG,日志文件记录的是数据的变化,而不是记录用户的操作。日志文件服务的是 SQL Server,而不是用户。

- DBCC OPENTRAN,返回当前数据库最久未被提交的事务。

- KILL 命令并不是百试不爽,如果一个连接正处于提交或者回滚的过程中,SQL Server 会尊重它的执行而不去强制终止它。

- 数据库自动收缩功能,会在空闲空间大于25%的情况下自动运行 DBCC SHRINKFILE 的动作。

- 对于一个比较繁忙的数据库,推荐的设置是开启数据库自动增长选项,以防数据库空间用尽,但要严格避免自动增长的发生。同时,尽量不要使用自动收缩功能。

- SQL Server 服务启动顺序

    1. 从注册表读取 SQL Server 启动信息。

    2. 检测硬件,配置内存与CPU

    3. 初始化MS DTC

    4. 系统数据库启动:master -> mssqlsystemresource -> model -> tempdb

    5. 准备网络连接。

    6. 启动 msdb 数据库和其他用户数据库。

- DBCC CHECKDB 命令:

  1. 检查数据库里有没有损坏发生。

  2. 尽力修复数据库损坏。

- DBCC CHECKDB 做些什么:

    1. 首先检查一些关键的系统表。(任意一张系统表上发生了损坏,DBCC CHECKDB都会直接失败,只有恢复数据库的备份)

    2. 对数据库运行DBCC CHECKALLOC。(检查数据库中所有页的分配)

    3. 对数据库中的每个表和视图运行DBCC CHECKTABLE

    4. 对数据库运行DBCC CHECKCATALOG

    5. 验证数据库中每个索引视图的内容。

    6. 验证数据库中的Service Broker数据。

- DBCC CHECKDB 修复选项:

    1. REPAIR_ALLOW_DATA_LOSS 尝试修复报告的所有错误。

    2. REPAIR_FAST 保留这是为了向后兼容,未执行任何修复操作。

    3. REPAIR_REBUILD 执行次要,快速修复(例如修复非聚集索引中的额外键)以及耗时修复(例如重新生成索引)。执行这些修复时不会有丢失数据的危险。

- REPAIR_ALLOW_DATA_LOSS 执行:

    1. DBCC CHECKDB 将由于 I/O 或校验错误而标记为不可访问的页重新标记为可访问,如同这些错误没有出现过一样。

    2. DBCC CHECKDB 将尝试使用常规的基于日志的恢复技术恢复数据库。

    3. 如果由于事务日志损坏而导致数据库恢复失败,则将重新生成事务日志。

- REPAIR_ALLOW_DATA_LOSS 无法修复的时候,应该怎么做?

    1. 按照预先的备份恢复策略,恢复数据库备份。

    2. 如果损坏发生在某些用户对象上(用户表,视图,存储过程等),可以把它们 DROP 掉试试。

    3. 将数据库设成紧急只读模式,用 SELECT...INTO 或其他方式,将数据导入到一个新建的空数据库里。

- ALTER DATABASE <DBNAME> SET EMERGENCY 设置数据库紧急模式。

- 缓冲区(Buffer Pool)管理是实现高效 I/O 操作的关键。一个缓冲区就是一个 8KB 的内存页,缓冲区高速缓存被划分为多个 8KB 页。

- 缓冲区管理器负责将数据页或索引页从数据库盘文件读入缓冲区高速缓存中,并将修复后的页写回磁盘。

- 脏页写入磁盘有三种方式:

    1. 惰性写入(Lazy writing):写入不经常使用的页,保证存在可用的缓冲区。

    2. 勤奋写入(Eager writing):立即写入。

    3. 检查点(Check Point):定时写入,保证在创建的检查点全部 脏页 都已写入磁盘。一般 SQL Server 会以一分钟左右的频率创建检查点。

- SQLIOSim 测试安装 SQL Server 服务器的 I/O 子系统是否没有问题。

- 通常建议把数据库文件和日志放在不同的物理磁盘上。如果可能的话,日志文件要放在写入速度比较快的磁盘上。

- 一个数据文件组可以有多个文件,并且放在不同的物理磁盘上。这样一来,I/O 工作会分布到不同的硬盘上。

- SQL Server 任务调度:

    1. 只有需要运行任务的连接才会被分配线程。空闲状态的连接,不会占用线程资源。

    2. 对于每一个CPU,SQL Server 内部会有一个Scheduler,由这个 Scheduler 决定在某个时间点,到底是哪个 SQL Server 线程去运行。

    3. 每个 Worker 跟一个线程(或纤程 Fiber)相对应,是 SQL Server 任务的执行单位。SQL Server 不直接调度线程/纤程,而是调度 Worker,使得 SQL Server 能够控制任务调度。

    4. 在 Worker 上运行的最小任务单元。最简单的 Task 就是一个简单 Batch

    5. SQL OS 调度算法的核心就是所有在逻辑 Scheduler 上运行的 Worker 都是非抢占式的。Worker 会始终在 Scheduler 上运行,直到它运行结束,或者主动将 Scheduler 让出给其他 Worker 为止。这个“让出”的动作,就是 Yielding

 注:

  1. 对于每个CPU,SQL Server 都会有一个 Scheduler 与之对应。在每个 Scheduler 里,会有若干个 Worker,对应于每个线程。

  2. 在客户端发过来请求之后,SQL Server 会将其分解成一个或多个 Task。根据每个 Scheduler 的繁忙程度,Task 会被分配到某个 Scheduler 上面。

  3. 如果 Scheduler 里有空闲的 Worker,Task 就会被分配到某个 Worker 上。如果没有,Scheduler 会创建新的 Worker,供 Task 使用。

  4. 如果 Scheduler 里的 Work 已经到了它的上限值,而它们都有 Task 要运行,那么新的 Task 只好进入等待 Worker 的状态。

- 锁:

    1. 共享锁:资源上存在共享锁(S锁)时,任何其他事务都不能修改数据。

    2. 更新锁:一次只有一个事务可以获得资源的更新锁(U锁)。事务真正修改数据时,将更新锁(U锁)转换为排他锁(X锁)。

    3. 排他锁:排他锁(X锁)可以防止并发事务对资源进行访问。使用排他锁(X锁)时,任何其他事务都无法读取或者修改数据;仅在使用 NOLOCK 提示或未提交读隔离级别时才会进行读取操作。

    4. 意向锁:数据库引擎使用意向锁来保护锁层次结构的底层资源,以防止其他事务对自己锁住的资源造成伤害,提高锁冲突检测性能。

    5.1 架构锁:数据库引擎在表数据定义语言(DDL)操作(例如添加列或删除表)的过程中使用架构修改(Sch-M)锁,阻止其他用户对这个表格的访问。

    5.2 架构锁:数据库引擎在编译和执行查询时使用架构稳定性(Sch-S)锁。Sch-S 锁不会阻止其他事务访问表格里的数据。但是,会阻止对表格做修改性的DDL操作和DML操作。

    6. 大容量更新锁:数据库引擎在将大容量复制到表中时使用大容量更新(BU)锁。它允许多个线程将数据并发地大容量加载到同一表,同时防止其他不进行大容量加载数据到进程访问该表。

 注:

  1. 数据修改语句(如 INSERTUPDATE 和 DELETE)合并了修改和读取操作。

  2. 语句在执行所需的修改操作之前首先执行读取操作以获取数据。因此,数据修改语句通常请求共享锁和排他锁。

  3. 例如,UPDATE 语句可能根据与一个表的联接修改另一个表中的行。在此情况下,除了请求更新行上的排他锁之外,UPDATE 语句还将请求在联接表中读取的行上的共享锁。

- 如果应用申请的锁粒度都比较小,产生阻塞的几率就会比较小。如果一个连接会经常申请页面级,表级,甚至是数据库一级的锁资源,程序产生阻塞的可能性就会很大。

- 如果一个连接总是能够非常快地把申请到的锁释放掉,那阻塞就不容易发生。如果它总是长时间地持有某些锁资源,那么就很容易发生阻塞了。

    1. 一个事务内部要访问或者修改的数据量越大,它所要申请的锁的数目就会越多,粒度也就可能越大。

    2. 一个事务做的事情越复杂,它要申请的锁的范围也就会越大。

    3. 一个事务延续的时间越长,它持有的锁的时间也会越长。

- 事务的隔离级别能影响锁的申请以及释放的时间;而语句的执行计划,也会影响到锁的粒度以及申请的数量。

- 并发控制:当许多人试图同时修改数据库中的数据时,必须实现一个控制系统,使一个人所做的修改不会对他人所做的修改产生负面影响。

- 隔离级别:

    1. 未提交读(READ UNCOMMITTED):语句可以读取已由其他事务修改但尚未提交的行。

    2. 已提交读(READ COMMITTED):语句不能读取已由其他事务修改但尚未提交的行。避免脏读)

    3. 可重复读(REPEATABLE READ):语句不能读取已由其他事务修改但尚未提交的行,并且其他任何事务都不能在当前事务完成之前读取的数据。(避免不可重复读)

    4. 可序列化(SERIALIZABLE):在可重复读的基础上,当前事务完成之前,其他事务不能使用当前事务中任何语句读取的键值范围之内插入新行。(避免幻读)

- 要确保有足够的索引,防止语句做全表扫描(因数据量多而导致锁增多),但是也要去掉那些对语句运行贡献不大的索引(因关联的索引被修改而导致锁增多)。

- PAGEIOLATCH_SH 和 PAGEIOLATCH_EX

    1. PAGEIOLATCH_SH 常常是发生在用户想要去访问一个数据页面,而 SQL Server 需要把这个页面从磁盘读往内存。

    2. PAGEIOLATCH_EX 常常是发生在用户对数据页面做了修改,SQL Server 要向磁盘回写。出现这个等待状态,意味磁盘的写入速度跟不上。

    3. WRITELOG 说明任务当前正在等待将日志写入日志文件。出现这个等待状态,意味着磁盘的写入速度跟不上。

- 用户请求的生命周期:

    1. 客户端向 SQL Server 发出请求指令,指令经过网络层,SQL Server 接收到。

    2. SQL Server 对收到的指令进行语法,语义检查,编译,生成新执行计划,或者找到缓存的执行计划重用。

    3. 运行指令

        - SQL Server 会首先为指令的运行申请内存。

        - 如果发现要访问的数据不在内存里,要将数据从磁盘读到内存中。如果发现内存里没有足够的空闲页面存放所有数据,还要做一些内存整理和 paging 动作,腾出足够的空间放数据。(PAGEIOLATCH_x

   - 按照执行计划,扫描或者 seek 内存中的数据页面,将指令需要处理的记录找出来。(LCK_x)

        - 指令可能还需要做一些联接或者计算的工作。(sum / max / sort

        - 根据指令内容,执行计划和数据量,SQL Server 可能还要在 tempdb 里创建一些对象,存放临时表,表变量,帮助做 join / sort 等。

        - 如果指令要修改数据记录,SQL Server 会修改内存缓冲区里页面的内容。

        - 如果指令发生数据修改,在提交事务之前,SQL Server 必须将相应的日志记录按照顺序写入日志文件。(WRITELOG

        - 将结果集返回给客户端。(ASYNC_NETWORK_IO

- 筛选条件的写法是有讲究的,最好都能够使用 SARG 的运算符,包括:=, >, <, >=, <=, IN, BETWEEN。有时还包括 LIKE(如LIKE'XXX%')。

- 对于不使用 SARG 运算符的表达式,SQL Server 对它们很难使用比较优化的做法,很可能就不使用索引。非 SARG 运算符包括(NOT, <>, NOT EXISTS, NOT IN, NOT LIKE 和内部函数 Convert, Upper 等)

- 尽可能限定语句的复杂度

    1. 动态语句

    2. 表格联接的数量

    3. 视图和存储过程的深度

    4. 不必要的排序和计算

    5. 超大结果集申请和返回

    6. 用多个简单语句替代一个复杂语句