【译】索引进阶(八):SQL SERVER唯一索引
- 【译注:此文为翻译,由于本人水平所限,疏漏在所难免,欢迎探讨指正】
原文链接:传送门。
在本章节我们检查唯一索引。唯一索引的特别之处在于它不仅提供了性能益处,而且提供了数据完整性益处。在SQL SERVER中,唯一索引是保证主键约束和候选键约束的唯一合理的方式。
唯一索引和约束
唯一索引与其他任何索引并没有什么不同,唯一不同之处在于它不允许多个条目具有相同的索引键值。既然索引中的一个条目映射到了交互表的一行,那么阻止条目被加入到索引中便是阻止了数据行被加到表中。这便是为什么唯一索引是主键约束和候选键约束的保证。
声明一个主键或者唯一性索引都会导致SQL SERVER自动创建索引。你可以具有一个唯一索引而没有与之匹配的约束,但是没有唯一索引的话,你不能具有任何唯一约束。定义一个约束会导致与约束同名的一个索引被创建。不首先删除约束,你是不能删除索引的,因为约束是不能离开索引而存在的。删除约束也会导致与之关联的索引被删除。
每张表包含超过一个唯一索引是可能的。举个例子,AdventureWork库的Product 表具有四个唯一索引,ProductID, ProductNumber, rowguid, ProductName这四个列各有一个唯一索引。AdventureWorks 数据库的设计者选择ProductID 作表的主键,其他三个作替换键,有时候 称为候选键。
你能够用CREATE INDEX 语句创建一个唯一索引,如下所示:
- CREATE UNIQUE NONCLUSTERED INDEX [AK_Product_Name]
- ON Production.Product ( [Name] );
也可以通过定义约束来创建唯一索引,如下所示:
- ALTER TABLE Production.Product
- ADD CONSTRAINT PK_Product_ProductID PRIMARY KEY CLUSTERED
- (
- ProductID );
在第一个例子中,你将确保不会有两个商品会有相同的商品名,在第二个例子中, 你将确保没有两个商品会有相同的ProductID 值。因为定义一个主键约束或者候选键约束会导致一个索引被创建,因此在约束的定义中你必须指定必要的索引信息,因此CLUSTERED关键字出现在上述ALTER TABLE 子句中。如果表包含有违反约束后者违反索引限制的数据,那么“CREATE INDEX ”语句会失败。
如果索引能够被创建,那么后续任何会 违反约束或索引的DML都会失败。举个例子,假设我们尝试着插入一条具有重复商品名的数据行,插入语句如下所示:
- INSERT Production.Product
- (
- Name, ProductNumber,
- Color,
- SafetyStockLevel, ReorderPoint, StandardCost,
- ListPrice, Size,
- SizeUnitMeasureCode, WeightUnitMeasureCode, [Weight],
- DaysToManufacture, ProductLine, Class, Style,
- ProductSubcategoryID, ProductModelID,
- SellStartDate, SellEndDate, DiscontinuedDate
- )
- VALUES
- (
- 'Full-Finger Gloves, M', 'A unique product number',
- 'Black',
- 4, 3, 20.00, 40.00, 'M',
- NULL, NULL, NULL,
- 0, 'M', NULL, 'U', 20, 3,
- GETDATE(), GETDATE(), NULL
- );
- 列表1:插入重复的商品名
这条语句会执行失败,我们会收到如下的错误信息:
- Msg 2601, LEVEL 14, State 1, Line 1
- Cannot INSERT duplicate KEY row IN object 'Production.Product'
- WITH UNIQUE INDEX  'AK_Product_Name'.
- The statement has been terminated.
这条信息告知我们AK_Product_Name 索引成功的保护了我们的表,它阻止了一个包含已经存在的商品名的数据行的非法插入。
在主键约束和唯一索引之间存在着一些细微的差异:
- 主键约束禁止null值,没有任何索引条目的索引键允许包含null值。而唯一性索引却允许null值。然而,既然唯一性索引认为两个null值是相互重复的,在每列中,只有一个包含null值的检索键值可以在索引中存在。
- 创建一个主键约束会导致创建一个聚集索引,除非以下事情发生:
- 表已经是一个聚集索引。
- 当你定义约束时候指定了NONCLUSTERED 关键字。
- 当你创建唯一约束并且表不是聚集索引时,创建唯一性约束会导致创建一个非聚集索引,除非你指定了CLUSTERED 关键字。
- 每张表只能有一个主键约束,但是可以有多个唯一性约束。
当决定是否创建一个唯一约束或者仅仅只是创建一个唯一索引时,请遵从MSDN库中的SQL SERVER文档:
- “There are no significant differences between creating a UNIQUE constraint and creating a unique index that is independent of a constraint. Data validation occurs in the same manner, and the query optimizer does not differentiate between a unique index created by a constraint or manually created. However, you should create a UNIQUE constraint on the column when data integrity is the objective. This makes the objective of the index clear.”
组合使用唯一索引和过滤索引
我们在上面提到的唯一索引的属性:它们只允许存在一个null值,通常与通用的商业规则会有冲突。通常我们会想要保证一个列中已经存在的值是唯一的,而允许那一列多行数据都可以没有任何值。
举例来说,假设你是某个商品供应商,你会从第三方厂商那边得到某些商品,并且你将自己的产品信息保存在一张ProductDemo表中,你为所有的商品都分配了一个ProductID 值,同时你也保留了商品的UPC(Universal Product Code) 值。然而并不是所有厂商的产品都会有一个UPC值,因此你的表会存在如下部分数据:
ProductID | UPCode | Other Columns |
(Primary Key) | (Unique, but not a key) | |
14AJ-W | 036000291452 | |
23CZ-M | ||
23CZ-L | ||
18MM-J | 044000865867 |
表1:ProductDemo表的部分内容
在第二列,你必须要确保UPC列的唯一性同时还需要允许null值的存在,提供这种功能的最好方式是组合使用唯一索引与过滤索引(过滤索引是第七章的主题,它在SQL SERVER 2008被引入)。
为了演示这个,我们创建了一个简单的表,其包含上面显示的列:
- CREATE TABLE ProductDemo
- (
- ProductID NCHAR(6) NOT NULL PRIMARY KEY,
- UPCode NCHAR(12) NULL
- );
现在我们向其插入4条空UP Code行,那么四条数据会被加入到表中,如下所示:
- INSERT ProductDemo (ProductID , UPCode) VALUES ('14AJ-W', '')
- , ('23CZ-M', NULL)
- , ('23CZ-L', NULL)
- , ('18MM-J', '');
然而当我们试着插入一行具有重复UPCode 值的数据行:
- INSERT ProductDemo (ProductID , UPCode) VALUES ('14AJ-K', '');
我们接收到了如下错误信息并且数据行没有被插入。
- Msg 2601, Level 14, State 1, Line 1
- Cannot insert duplicate key row in object 'dbo.ProductDemo' with unique index 'AK_UPCode'.
- The statement has been terminated.
如同在本章之前的例子所做的那样,你可以选择指定IGNORE_DUP_KEY 选项。因此,我们原始的 CREATE INDEX 语句没有经过措辞:
- CREATE UNIQUE NONCLUSTERED INDEX AK_Product_Name
- ON Production.Product ( [Name] )
- WITH ( IGNORE_DUP_KEY = OFF );
这个选项的名称使人产生一些误解,因为当存在唯一索引时,重复的键值从来不会被忽略。更确切的说,唯一索引从来不会允许重复的键值。这个选项控制着插入多行数据时候可执行的行为。举个例子,你有两张表:表A和表B,它们有相同的表架构,那么你可以向SQL SERVER提交如下插入语句:
- INSERT INTO TableA
- SELECT *
- FROM TableB;
SQL SERVER会尝试复制所有表B的数据行到表A,如果 表B的两条数据因为重复的值而不能被复制到表A会怎么样呢?你是希望其他所有行被复制,只有重复的两行失败呢,还是希望整个插入语句都会失败?
这个选择将由你来做。当你创建唯一索引时候,你将决定发生什么,当一个插入语句企图向一个唯一索引中插入多个重复键值时。IGNORE_DUP_KEY 的两个设置列举如下:
- IGNORE_DUP_KEY = OFF
整个插入语句都会失败,并列出一个错误信息,注意,此选项是默认的。
- IGNORE_DUP_KEY = ON
只有与已存在的行索引键值重复的行才会失败,并列出一个告警信息,注意,如果一个唯一索引同时又是过滤索引那么这个选项不能被设置。
IGNORE_DUP_KEY 这个选项仅仅影响INSERT 语句,它会被UPDATE,CREATE INDEX, ALTER INDEX 语句忽略,当给一个表添加主键索引或者唯一索引时,也可以指定IGNORE_DUP_KEY 选项。
为什么唯一索引能提供意想不到的益处?
唯一索引能提供意料之外的性能益处。这是因为它们提供给SQL SERVER一些我们视为理所当然而SQL SERVER绝不会假设的信息,AdventureWork数据库Product 表的两个唯一索引:ProductID 和ProductName提供了证明了这个例子。
假设你收到了一个来自仓库员工的请求,这个查询会展示给他们一些Product 表中各个产品的如下信息:
- 产品名
- 产品被出售的次数
- 这些销售的总价值
为了解决此问题,你写出了如下查询:
- SELECT [Name]
- , COUNT(*) AS 'RowCount'
- , SUM(LineTotal) AS 'TotalValue'
- FROM Production.Product P
- JOIN Sales.SalesOrderDetail D ON D.ProductID = P.ProductID
- GROUP BY ProductID
仓库员工非常开心看到这个查询,因为它给了他们想要的结果。每个产品一行数据,每行包含了产品名,销售次数和总销售额。部分输出如下所示:
- Name RowCount TotalValue
- ---------------------------------- ----------- ----------------------------------
- Sport-100 Helmet, Red 3083 157772.394392
- Sport-100 Helmet, Black 3007 160869.517836
- Mountain Bike Socks, M 188 6060.388200
- Mountain Bike Socks, L 44 513.000000
- Sport-100 Helmet, Blue 3090 165406.617049
- AWC Logo Cap 3382 51229.445623
- Long-Sleeve Logo Jersey, S 429 21445.710000
- Long-Sleeve Logo Jersey, M 1218 115249.214976
- Long-Sleeve Logo Jersey, L 1635 198754.975360
- Long-Sleeve Logo Jersey, XL 1076 95611.197080
- HL Road Frame - Red, 62 218 394255.572400
然而,你关心于这个查询的潜在消耗,SalesOrderDetail 是这个查询所涉及的表中较大的那个,并且它的数据行必须按照产品名称分组,而这个产品名存在于Product 表,并不在SalesOrderDetail 表中。通过SSMS,你注意到SalesOrderDetail 表在它的主键上是聚集的(SalesOrderID / SalesOrderDetailID),当试着按产品名称分组时候这并不会产生任何益处。
如果你运行第五章的代码:包含列,你在SalesOrderDetail.ProductID 外键上创建了如下的非聚集索引:
- CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDate
- ON Sales.SalesOrderDetail
- (
- ProductID,
- ModifiedDate
- )
- INCLUDE
- (
- OrderQty,
- UnitPrice,
- LineTotal
- );
你感觉这个索引会对你的查询有所帮助,因为它包含了你的查询需要的所有信息,除了产品名之外,它按ProductID 的顺序排列,但是你仍关心于把一个表的信息按照另一个不同表的值进行分组。
你返回到SSMS,打开“显示实际的执行计划”选项,运行查询,注意到执行计划显示如下图:
首先你很惊奇于看到Product表的产品名称索引,Product.AK_Product_Name从来没有使用过,即使它的列是GROUP BY子句的聚合键。那么你意识到 Product.Name有一个唯一索引和 Product.ProductID有一个唯一索引会告知SQL SERVER每一个product name和每一个product id都会有一个产品,因此e, GROUP BY [Name] 或者GROUP BY ProductID 是一样的分组,也就是说,它们都会一个产品产生一个分组。
因此查询优化器认为你的查询和如下的查询是相同的:
- SELECT[Name]
- , COUNT(*)AS 'RowCount'
- , SUM(LineTotal)AS 'TotalValue'
- FROM Production.Product P
- JOIN Sales.SalesOrderDetail D OND.ProductID= P.ProductID
- GROUP BY ProductID
因此两个ProductID索引会同时支持对所请求数据的连接和分组操作。
SQL SERVER 能够同时扫描SalesOrderDetail 表上的覆盖索引和Product 表的聚集索引,它们两者都是以ProductID的顺序排列的,产生每个分组的总数,然后以产品名称合并,并不需要做任何排序或者哈希,简而言之,SQL SERVER为你的查询产生尽可能高效的执行计划。
如果你移除Product.AK_Product_Name 索引,像这样:
- IF EXISTS ( SELECT *
- FROM sys.indexes
- WHERE OBJECT_ID = OBJECT_ID(N'Production.Product')
- AND name = N'AK_Product_Name')
- DROP INDEX AK_Product_Name
- ON Production.Product;
那么新的执行计划(如下所示)并不高效,它需要额外的排序和合并操作。
你能够看到尽管唯一索引的主要目的是提供了数据的一致性,它也能够帮助查询优化器决定收集数据的最高效的方式,即使那个索引并不是设计用来访问数据的。
结论
唯一索引为主键约束和候选键约束提供支持。唯一索引可以与其交互的约束一起存在,但唯一约束不能离开它的索引而存在。
唯一索引可以同时是一个过滤索引(filtered index),这保证了唯一性的列可以允许多个null值存在。
IGNORE_DUP_KEY选项影响了多行插入语句的行为。
唯一索引能够提供更好的查询性能,即使索引并没有被使用。
【译】索引进阶(八):SQL SERVER唯一索引的更多相关文章
- T-SQL查询进阶--理解SQL Server中索引的概念,原理以及其他
简介 在SQL Server中,索引是一种增强式的存在,这意味着,即使没有索引,SQL Server仍然可以实现应有的功能.但索引可以在大多数情况下大大提升查询性能,在OLAP中尤其明显.要完全理解索 ...
- T-SQL查询进阶--理解SQL Server中索引的概念,原理
简介 在SQL Server中,索引是一种增强式的存在,这意味着,即使没有索引,sql server仍然可以实现应有的功能,但索引可以在大多数情况下提升查询性能,在OLAP(On line Trans ...
- c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程
c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...
- 理解SQL Server中索引的概念
T-SQL查询进阶--理解SQL Server中索引的概念,原理以及其他 简介 在SQL Server中,索引是一种增强式的存在,这意味着,即使没有索引,SQL Server仍然可以实现应有的功能 ...
- [转]SQL SERVER整理索引碎片测试
SQL SERVER整理索引碎片测试 SQL SERVER整理索引的方法也就这么几种,而且老是自作聪明的加入智能判断很不爽,还是比DBMS_ADVISOR差远了: 1SQL SERVER 2000/2 ...
- 【译】SQL Server索引进阶第八篇:唯一索引
原文:[译]SQL Server索引进阶第八篇:唯一索引 索引设计是数据库设计中比较重要的一个环节,对数据库的性能其中至关重要的作用,但是索引的设计却又不是那么容易的事情,性能也不是那么轻易就 ...
- SQL Server索引进阶:第十级,索引内部结构
原文地址: Stairway to SQL Server Indexes: Level 10,Index Internal Structure 本文是SQL Server索引进阶系列(Stairway ...
- SQL Server索引进阶:第一级,索引简介
这个并不是我翻译的,全文共有15篇,但我发现好多网站已经不全,所以自己整理. 原文地址: Stairway to SQL Server Indexes: Level 1, Introduction t ...
- SQL Server 数据库索引
原文:SQL Server 数据库索引 一.什么是索引 减少磁盘I/O和逻辑读次数的最佳方法之一就是使用[索引] 索引允许SQL Server在表中查找数据而不需要扫描整个表. 1.1.索引的好处: ...
随机推荐
- MR-join连接
package com.bw.mr; import java.io.BufferedReader; import java.io.IOException; import java.io.InputSt ...
- storybook实践
很久之前就听说过storybook,一直想实践一下
- Docker 核心技术之Dockerfile
Dockerfile 简介 什么是Dockerfile Dockerfile其实就是根据特定的语法格式撰写出来的一个普通的文本文件 利用docker build命令依次执行在Dockerfile中定义 ...
- 数据定义语言(DDL Data Definition Language)基础学习笔记
创建数据库 create database if not exists STUDY character set utf8 ; 查看新建数据库的语句 SHOW CREATE DATABASE STUDY ...
- Centos 6.5下mysql 8.0.11的rpm包的安装方式
1.系统版本及mysql下载地址 操作系统:Centos 6.5(Centos 7.4下载对应的mysql版本安装同理) mysql数据库版本:mysql8.0.11 mysql官方网站:http:/ ...
- Mysql 查询当月时间数据
SELECTDATE_FORMAT(CURDATE(), '%Y%m'), DATE_FORMAT(t.transactiontime, '%Y%m'),t.*FROM ttransactions t ...
- 什么是SerDes,serializer/deserializer?
序列化器与反序列化器(SerDes,serializer/deserializer)是一种进行串行数据和并行数据相互转换的收发集成电路(IC).发送器部分是一个串行到并行的转换器,接收器部分是一个并行 ...
- python自动启动appium服务
我们可以通过socket模块来检查appium是否启动,如果未启动,可以通过subprocess模块来启动,需要导入socket和subprocess模块,具体如下图: 第1个函数:表示检查appiu ...
- CTF--web
https://adworld.xctf.org.cn/task/task_list?type=web&number=3&grade=0 1.view source 查看源代码 1.鼠 ...
- 核心类生成-Mybatis Generator的使用
总结一下Generator的使用,首先要设计好数据表,然后修改generator.xml中的配制,接着直接运行命令就可以了. 第一步:数据库设计: 生成数据表代码: /* Navicat MySQL ...