前言


终于进入死锁系列,前面也提到过我一直对隔离级别和死锁以及如何避免死锁等问题模棱两可,所以才鼓起了重新学习SQL Server系列的勇气,本节我们来讲讲SQL Server中的死锁,看到许多文章都只简述不能这样做,这样做会导致死锁,但是未理解其基本原理,下次遇到类似情况依然会犯错,所以基于了解死锁原理并且得到治疗死锁良方,博主不惜花费多天时间来学习死锁最终总结出本文,若有叙述不当之处请在评论中指出。

死锁定义


死锁是两个或多个进程互相阻塞的情况。两个进程死锁的例子是,进程A阻塞进程B且进程B阻塞进程A。涉及多个进程死锁的例子是,进程A阻塞进程B,进程B阻塞进程C且进程C阻塞进程A。在任何一种情况下,SQL Server检测到死锁,都会通过终止其中的一个事务尽心干预。如果SQL Server不干预,涉及的进程永远陷于死锁状态。

除外另外指定,SQL Server选择终止工作最少的事务,因为它便于回滚该事务的工作。但是,SQL Server允许用户设置一个叫做DEADLOCK_PRIORITY的会话选项,可以是范围在-10~10之间的21个值中的任意值,死锁优先级最低的进程将被作为牺牲对象,而不管其做了多少工作。我们可以举一个生活中常见和死锁类似的例子,当在车道上行驶时,快到十字路口的红灯时,此时所有的小车都已经就绪等待红灯,当变绿灯时,此时有驾驶员发现走错了车道,于是开始变换车道,但是别的车道都拥堵在一块根本插不进去,驾驶员只有等待有空隙时再插进去,同时驾驶员车道上后面的小车又在等待驾驶员开到别的车道。这种情况虽然不恰当,但是在一定程度上很好的表现了死锁的情况,所以在开车时尽量别吵吵,否则谁都走不了,keep silence。

下面我们来演示常见的一种死锁情况,然后我们再来讨论如何减少系统中死锁的发生。

读写死锁


在SQL Server数据库中我们打开两个连接并确保都已连接到数据库,在会话一中我们试图去更新Production.Products表中产品2的行。

SET TRAN ISOLATION LEVEL READ COMMITTED 

BEGIN TRAN;

UPDATE Production.Products
SET unitprice += 1.00
WHERE productid = 2;

在会话2中再来打开一个事务,更新Sales.OrderDetails表中产品2的行,并使事务保持打开状态

SET TRAN ISOLATION LEVEL READ COMMITTED

BEGIN TRAN

UPDATE Sales.OrderDetails
SET unitprice += 1.00
WHERE productid = 2;

此时上述会话一和会话二都用其会话的排他锁且都能更新成功,下面我们再来在会话一中进行查询Sale.OrderDetails表中产品2的行并提交事务。

SET TRAN ISOLATION LEVEL READ COMMITTED

BEGIN TRAN;

SELECT orderid, productid, unitprice
FROM Sales.OrderDetails
WHERE productid = 2; COMMIT TRAN;

因为需要查询Sales.OrderDetails表中产品2的行,但是在之前我们更新产品2的行同时并未提交事务,因为查询的共享锁和排它锁不兼容,最终导致查询会阻塞,接下来我们在会话二中再来查询Producution.Products表中产品为2的行。

SET TRAN ISOLATION LEVEL READ COMMITTED

BEGIN TRAN

SELECT productid, unitprice
FROM Production.Products
WHERE productid = 2; COMMIT TRAN;

此时我们看到在会话二中能成功查询到Production.Products表中产品2的行,同时我们再来看看会话一中查询情况。

上述死锁算是最常见的死锁情况,在会话一(A进程)中去更新Production.Products表中产品2的行,在会话二(B进程)去更新Sales.OrderDetails表中产品2的行,但是接下来在会话一中去查询Sales.OrderDetails表中产品2的行,此时B进程要等待A进程中未提交的事务进行提交,所以导致A进程将阻塞B进程,接着在会话二中去查询Production.Products表中产品2的行,此时A进程要等待B进程中未提交的事务进行提交,所以导致B进程阻塞A进程,最终结果将是死锁。所以到了这里我们能够很清楚地知道在两个或多个事务中注意事务之间不能交叉进行。要是面试时忘记了肿么办,告诉你一个简单的方法,当军训或者上体育课正步走时只有1、2、1,没有1、2、2就行。

写写死锁


想必大家大部分只知道上述情况的死锁,上述情况是什么情况,我们抽象一下则是不同表之间导致的死锁,下面我们来看看同一表中如何产生死锁,这种情况大家更加需要注意了。我们首先创建死锁测试表并对表中列Id,Name创建唯一聚集索引,如下:

USE tempdb
GO
CREATE TABLE DeadlocksExample
(Id INT, Name CHAR(20), Company CHAR(50));
GO
CREATE UNIQUE CLUSTERED INDEX deadlock_idx ON DeadlocksExample (Id, Name)
GO

接下来在会话一中插入一条测试数据开启事务但是并未提交,如下:

BEGIN TRAN
INSERT INTO dbo.DeadlocksExample
VALUES (1, 'Jeffcky', 'KS')

接下来再来打开一个会话二插入一条数据开启事务但是并未提交,如下:

BEGIN TRAN
INSERT INTO DeadlocksExample
VALUES (10, 'KS', 'Jeffcky')

再来在会话一中插入一条数据。

INSERT INTO DeadlocksExample
VALUES (10, 'KS', 'Jeffcky')

此时此次插入将会阻塞,如下:

最后再来在会话二中插入一条数据

INSERT INTO DeadlocksExample
VALUES (1, 'Jeffcky', 'KS')

此时此次插入能进行但是会显示死锁信息,如下:

想必大多数情况下看到的是通过不同表更新行产生的死锁,在这里我们演示了在相同表通过插入行也会导致死锁,死锁真是无处不在。上述发生死锁的主要原因在于第二次在会话一中去插入相同数据行时此时由于我们创建了Id和Name的唯一聚集索引所以SQL Server内部会尝试去读取行导致插入阻塞,在会话一中去插入行同理,最终造成彼此等待而死锁。为了更深入死锁知识,我们来看看如何从底层来探测死锁,上述发生死锁后,我们通过运行如下语句来查询死锁图:

SELECT XEvent.query('(event/data/value/deadlock)[1]') AS DeadlockGraph
FROM ( SELECT XEvent.query('.') AS XEvent
FROM ( SELECT CAST(target_data AS XML) AS TargetData
FROM sys.dm_xe_session_targets st
JOIN sys.dm_xe_sessions s
ON s.address = st.event_session_address
WHERE s.name = 'system_health'
AND st.target_name = 'ring_buffer'
) AS Data
CROSS APPLY
TargetData.nodes
('RingBufferTarget/event[@name="xml_deadlock_report"]')
AS XEventData ( XEvent )
) AS src;

此时你将发现会出现如下xml的数据:

我们点看死锁图来分析分析:

<deadlock>
<victim-list>
<victimProcess id="process17602d868" />
</victim-list>
<process-list>
<process id="process17602d868" taskpriority="0" logused="300" waitresource="KEY: 2:2089670228247904256 (4e0d37de3c51)" waittime="4222" ownerId="49122" transactionname="user_transaction" lasttranstarted="2017-03-04T21:56:15.447" XDES="0x16db8c3a8" lockMode="X" schedulerid="4" kpid="8296" status="suspended" spid="59" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2017-03-04T21:56:47.080" lastbatchcompleted="2017-03-04T21:56:15.450" lastattention="1900-01-01T00:00:00.450" clientapp="Microsoft SQL Server Management Studio - 查询" hostname="WANGPENG" hostpid="1640" loginname="wangpeng\JeffckyWang" isolationlevel="read committed (2)" xactid="49122" currentdb="2" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="84" sqlhandle="0x02000000ea13d9115e8a4d429bc3d549e9053a3a784358020000000000000000000000000000000000000000">
INSERT INTO [DeadlocksExample] values(@1,@2,@3) </frame>
<frame procname="adhoc" line="1" sqlhandle="0x020000009882c20809f279b6638fea1ef34b7986efb6b60a0000000000000000000000000000000000000000">
INSERT INTO DeadlocksExample
VALUES (1, 'Jeffcky', 'KS') </frame>
</executionStack>
<inputbuf>
INSERT INTO DeadlocksExample
VALUES (1, 'Jeffcky', 'KS') </inputbuf>
</process>
<process id="process17602dc38" taskpriority="0" logused="300" waitresource="KEY: 2:2089670228247904256 (381c351990d5)" waittime="20467" ownerId="49022" transactionname="user_transaction" lasttranstarted="2017-03-04T21:56:06.070" XDES="0x16db8d6a8" lockMode="X" schedulerid="4" kpid="2684" status="suspended" spid="54" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2017-03-04T21:56:30.837" lastbatchcompleted="2017-03-04T21:56:06.070" lastattention="1900-01-01T00:00:00.070" clientapp="Microsoft SQL Server Management Studio - 查询" hostname="WANGPENG" hostpid="1640" loginname="wangpeng\JeffckyWang" isolationlevel="read committed (2)" xactid="49022" currentdb="2" lockTimeout="4294967295" clientoption1="671090784" clientoption2="390200">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="84" sqlhandle="0x02000000ea13d9115e8a4d429bc3d549e9053a3a784358020000000000000000000000000000000000000000">
INSERT INTO [DeadlocksExample] values(@1,@2,@3) </frame>
<frame procname="adhoc" line="1" sqlhandle="0x02000000579d610429b0df7caee58044a9e5b493ea0d8e450000000000000000000000000000000000000000">
INSERT INTO DeadlocksExample
VALUES (10, 'KS', 'Jeffcky') </frame>
</executionStack>
<inputbuf>
INSERT INTO DeadlocksExample
VALUES (10, 'KS', 'Jeffcky') </inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="2089670228247904256" dbid="2" objectname="tempdb.dbo.DeadlocksExample" indexname="1" id="lock172b46e80" mode="X" associatedObjectId="2089670228247904256">
<owner-list>
<owner id="process17602dc38" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process17602d868" mode="X" requestType="wait" />
</waiter-list>
</keylock>
<keylock hobtid="2089670228247904256" dbid="2" objectname="tempdb.dbo.DeadlocksExample" indexname="1" id="lock172b48b00" mode="X" associatedObjectId="2089670228247904256">
<owner-list>
<owner id="process17602d868" mode="X" />
</owner-list>
<waiter-list>
<waiter id="process17602dc38" mode="X" requestType="wait" />
</waiter-list>
</keylock>
</resource-list>
</deadlock>

东西貌似比较多哈,别着急我也是菜鸟,我们慢慢看,我们将其折叠,重点是分为如下两块:

死锁最重要的两个节点则是如上process和resource,我们再来一块一块分析,首先看process-list

如上我们能够很清晰的看到关于死锁的所有细节,我们查询的SQL语句、隔离级别以及事务开始和结束的时间等更多详细介绍,我们再来看看resource-list

而resource-list则列举出了关于锁的所有资源,如上列举出了每个进程获取到的锁以及请求的锁。我们从上看出通过 objectname 来标识数据库关于死锁的表,我们可以通过 associatedObjectId 关联对象Id来得到表明和索引,运行如下查询:

SELECT OBJECT_NAME(p.object_id) AS TableName ,
i.name AS IndexName
FROM sys.partitions AS p
INNER JOIN sys.indexes AS i ON p.object_id = i.object_id
AND p.index_id = i.index_id
WHERE partition_id = 2089670228247904256

上述resource-list节点下有两个重要的子节点:owner-list和waiter-list,owner-list从字面意思理解则是拥有锁的进程,同理waiter-list则是请求锁并且等待这个拥有锁的进程释放的进程。我们能够看到上述涉及到的都是排它锁。resource-list中的过程大概如下:

  • (1)进程dc38获取在表 DeadlocksExample 上键中的排它锁。
  • (2)进程d868获取在表 DeadlocksExample 上键中的排它锁。
  • (3)进程d868请求在表 DeadlocksExample 上键中的排它锁。
  • (4)进程dc38请求在表 DeadlocksExample 上键中的排它锁。

所以为何一般不推荐使用联合主键,若使用联合主键则该情况如上述所述,此时两个列默认创建则是唯一聚集索引,当有并发情况产生时会就有可能导致在同一表中插入相同的值此时将导致死锁情况发生,想必大部分使用联合主键的情景应该是在关联表中,将两个Id标识为联合主键,此时我们应该重新设置一个主键无论是INT或者GUID也好都比联合主键要强很多。

实战拓展


上述我们大概了解了下死锁图以及相关节点解释,接下来我们来演示几种常见的死锁并逐步分析。我们来看看。

避免逻辑死锁


我们创建如下测试表并默认插入数据:

CREATE TABLE Table1
(
Column1 INT,
Column2 INT
)
GO
INSERT INTO Table1 VALUES (1, 1), (2, 2),(3, 3),(4, 4)
GO CREATE TABLE Table2
(
Column1 INT,
Column2 INT
)
GO
INSERT INTO Table2 VALUES (1, 1), (2, 2),(3, 3),(4, 4)
GO

此时我们进行数据更新对Column2,如下:

UPDATE Table1 SET Column1 = 3 WHERE Column2 = 1

此时由于我们对列Column2没有创建索引,所以会造成SQL Server引擎会进行全表扫描去堆栈中找到我们需要更新的数据,同时呢SQL Server会对该行更新的数据获取一个排它锁,当我们进行如下查询时

SELECT Column1 FROM Table1
WHERE Column2 = 4

此时将获取共享锁,即使上述更新和此查询语句在不同的会话中都不会造成阻塞,虽然排它锁和共享锁不兼容,因为上述更新数据的内部事务已经提交,所以排它锁已经释放。下面我们来看看死锁情况,我们打开两个会话并确保会话处于连接状态。

在会话一中我们对表一上的Column1列进行更新通过筛选条件Column2同时开启事务并未提交,如下:

BEGIN TRANSACTION

UPDATE Table1 SET Column1 = 3 WHERE Column2 = 1

会话二同理

BEGIN TRANSACTION

UPDATE Table2 SET Column1 = 5 WHERE Column2 = 2

同时去更新两个会话中的数据。接下来再在会话一中查询表二中的数据行,如下:

BEGIN TRANSACTION

SELECT Column1 FROM Table2
WHERE Column2 = 3 ROLLBACK TRANSACTION

在读写死锁中我们已经演示此时查询会造成堵塞,就不再贴图片了。
同理在会话二中查询表一中的数据行。

BEGIN TRANSACTION

SELECT Column1 FROM Table1
WHERE Column2 = 4 ROLLBACK TRANSACTION
GO

此时运行会话二中的语句,将得到如下死锁信息,当然二者必然有一个死锁牺牲品,至于是哪个会话,那就看SQL Server内部处理机制。

上述关于表一和表二死锁的情况,大概如下图所示

上述由于没有建立索引导致全表扫描所以对于每行记录都会获取一个共享锁,但是呢,更新数据进程又未提交事务最终会导致死锁,其实我们可以通过建立索引来找到匹配的行同时会绕过在叶子节点上被锁定的行,那么应该创建什么索引呢,对筛选条件创建过滤索引?显然是不行的,因为查询出的列还是到基表中去全表扫描,所以我们常见覆盖索引,如下:

CREATE NONCLUSTERED INDEX idx_Column2 ON Table1(Column2) INCLUDE(column1)
CREATE NONCLUSTERED INDEX idx_Column2 ON Table2(Column2) INCLUDE(column1)

当我们再次重新进行如上动作时,你会发现在会话一中进行查询时此时将不会导致阻塞,直接能查询出数据,如下:

所以通过上述表述我们知道好的索引设计能够减少逻辑冲突上的死锁。

避免范围扫描和SERIALIZABLE死锁


比如我们要进行如下查询。

SELECT CustomerIDFROM Customers WHERE CustomerName = @p1

一旦有并发则有可能造成幻影读取即刚开始数据为空行,但是第二次读取时则存在数据,所以此时我们设置更高的隔离级别,如下:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

通过设置该隔离级别即使刚开始数据为空行,它能保证再次读取时返回的数据一定为空行,通过锁住 WHERE CustomerName = @p1 并且它会锁住值等于@p1的所有记录,我们经常有这样的需求,当数据存在时则更新,不存在时则插入,如果你没有想到并发情况的发生,估计到时投诉将落在你身上,所以为了解决两次读取一致的情况我们设置最高隔离级别,如下:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION
IF EXISTS ( SELECT 1
FROM [dbo].[Customers] WITH ( ROWLOCK )
WHERE CustomerName = @p1 )
UPDATE dbo.Customers
SET LatestOrderStatus = NULL ,
OrderLimit = 0
WHERE CustomerName = @p1;
ELSE
INSERT INTO dbo.Customers
( CustomerName ,
RegionID ,
OrderLimit ,
LatestOrderStatus
)
VALUES ( @p1 ,
0 ,
0 ,
NULL
);
COMMIT TRANSACTION

上述假设我们对CustomerName建立了唯一索引并加了行锁来锁住单行数据,看起来so good,实际上有没有问题呢。如果当CustomerName = 'Jeffcky'在该行上面的CustomerName = 'Jeffcky1',在其下方的CustomerName = 'Jeffcky2',此时通过设置最高隔离级别将锁住这三行来阻止任何数据的插入,我们可以将其叫做范围共享锁,如果在不同会话中在该范围内插入不同行,此时极有可能造成死锁,你以为设置最高隔离级别就万事大吉了吗。那么该如何解决这个麻烦呢,我们可以通过Merge来避免该死锁,因为Merge操作为单原子操作,我们不在需要最高隔离级别,但是貌似有潜在的bug发生未验证过,同时该Merge我也未用过。

这个问题是在博问中看到dudu老大提出插入重复数据而想到(博问地址:https://q.cnblogs.com/q/90745/),dudu老大所给语句为如下SQL语句:

IF NOT EXISTS(SELECT 1 FROM [Relations] WHERE [UserId]=@UserId AND [RelativeUserId]=@RelativeUserId AND IsActive=1)
BEGIN
BEGIN TRANSACTION
INSERT INTO [Relations]([UserId], [RelativeUserId])
VALUES (@UserId,@RelativeUserId)
UPDATE [Users] SET FollowingCount=FollowingCount+1 WHERE UserID=@UserId
UPDATE [Users] SET FollowerCount=FollowerCount+1 WHERE UserID=@RelativeUserId
COMMIT TRANSACTION
END

当时我所给出的答案为如下:

INSERT INTO.....SELECT ...FROM WHERE NOT EXSITS(SELECT 1 FROM...)

对应上述情况我们将上述隔离级别去掉利用两个语句来操作,如下:

UPDATE  dbo.Customers
SET LatestOrderStatus = NULL ,
OrderLimit = 0
WHERE CustomerName = @p1; INSERT INTO dbo.Customers
( CustomerName ,
RegionID ,
OrderLimit ,
LatestOrderStatus
)
SELECT @p1~ ,
0 ,
0 ,
NULL
WHERE NOT EXISTS ( SELECT 1
FROM dbo.Customers AS c
WHERE CustomerName = @p1 )

此时没有事务,上述虽然看起来很好不会引起死锁但是对于插入操作会导致阻塞。我看到上述dudu老大提出的问题有如下答案:

BEGIN TRANSACTION
IF NOT EXISTS(SELECT 1 FROM [Relations] WITH(XLOCK,ROWLOCK) WHERE [UserId]=@UserId AND [RelativeUserId]=@RelativeUserId AND IsActive=1)
BEGIN
INSERT INTO [Relations]([UserId], [RelativeUserId])
VALUES (@UserId,@RelativeUserId)
UPDATE [Users] SET FollowingCount=FollowingCount+1 WHERE UserID=@UserId
UPDATE [Users] SET FollowerCount=FollowerCount+1 WHERE UserID=@RelativeUserId
END
COMMIT TRANSACTION

经查资料显示对于XLOCK,SQL Server优化引擎极有可能忽略XLOCK提示,而导致无法解决问题,具体未经过验证。在这里我觉得应该使用UPDLOCK更新锁。通过UPDLOCK与其他更新锁不兼容,通过UPDLOCK来序列化整个过程,当运行第二个进程时,由于第一个进程占用锁导致阻塞,所以直到第一个进程完成整个过程第二个进程都将处于阻塞状态,所以对于dudu老大提出的问题是否最终改造成如下操作呢。

BEGIN TRANSACTION

IF NOT EXISTS(SELECT 1 FROM [Relations] WITH (ROWLOCK, UPDLOCK) WHERE [UserId]=@UserId AND [RelativeUserId]=@RelativeUserId AND IsActive=1)
UPDATE [Users] SET FollowingCount=FollowingCount+1 WHERE UserID=@UserId;
UPDATE [Users] SET FollowerCount=FollowerCount+1 WHERE UserID=@RelativeUserId;
ELSE
INSERT INTO [Relations]
    ( [UserId],
    [RelativeUserId]
 )
VALUES ( @UserId,
     @RelativeUserId
  ); COMMIT TRANSACTION

总结


本节我们比较详细讲解了SQL Server中的死锁以及避免死锁的简单介绍,对于如何避免死锁我们可以从以下来看。

  • (1)锁保持的时间越长,增加了死锁的可能性,尽量缩短事务的时间即尽量使事务简短。
  • (2)事务不要交叉进行,按照顺序执行。
  • (3)对于有些逻辑可能不可避免需要交叉进行事务,此时我们可能通过良好的索引设计来规避死锁发生。
  • (4)我们也可以通过try..catch,捕获事务出错并retry。
  • (5)最后则是通过设置隔离级别来减少死锁频率发生。

原文链接

SQL Server-聚焦深入理解死锁以及避免死锁建议(转载)的更多相关文章

  1. Webservice WCF WebApi 前端数据可视化 前端数据可视化 C# asp.net PhoneGap html5 C# Where 网站分布式开发简介 EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下? SQL Server之深入理解STUFF 你必须知道的EntityFramework 6.x和EntityFramework Cor

    Webservice WCF WebApi   注明:改编加组合 在.net平台下,有大量的技术让你创建一个HTTP服务,像Web Service,WCF,现在又出了Web API.在.net平台下, ...

  2. [转帖]SQL Server 10分钟理解游标

    SQL Server 10分钟理解游标 https://www.cnblogs.com/VicLiu/p/11671776.html 概述 游标是邪恶的! 在关系数据库中,我们对于查询的思考是面向集合 ...

  3. SQL Server里因丢失索引造成的死锁

    在今天的文章里我想演示下SQL Server里在表上丢失索引如何引起死锁(deadlock)的.为了准备测试场景,下列代码会创建2个表,然后2个表都插入4条记录. -- Create a table ...

  4. SQL Server之深入理解STUFF

    前言 最近项目无论查询报表还是其他数据都在和SQL Server数据库打交道,对于STUFF也有了解,但是发现当下一次再写SQL语句时我还得查看相关具体用法,说到底还是没有完全理解其原理,所以本节我们 ...

  5. 理解SQL Server中索引的概念,原理以及其他(转载)

    简介 在SQL Server中,索引是一种增强式的存在,这意味着,即使没有索引,SQL Server仍然可以实现应有的功能.但索引可以在大多数情况下大大提升查询性能,在OLAP中尤其明显.要完全理解索 ...

  6. SQL Server 2008 收缩日志 清空删除大日志文件 转载

    SQL Server 2008 收缩日志 清空删除大日志文件 由于SQL2008对文件和日志管理进行了优化,所以以下语句在SQL2005中可以运行但在SQL2008中已经被取消:(SQL2005)Ba ...

  7. SQL Server 2008 Datetime Cast 成 Date 类型可以使用索引(转载)

    很久没写blog,不是懒,实在是最近我这的访问速度不好,用firefox经常上传不了图片 ....... 今天无意发现了SQL Server 2008 Datetime Cast 成 Date 类型可 ...

  8. SQL Server性能优化(6)查询语句建议

    1. 如果对数据不是工业级的访问(允许脏读),在select里添加 with(nolock) ID FROM Measure_heat WITH (nolock) 2. 限制结果集的数据量,如使用TO ...

  9. SQL Server 当表分区遇上唯一约束(转载)

    一.前言 我已经在高兴对服务器创建了表分区并且获得良好性能和自动化管理分区切换的时候,某一天,开发人员告诉我,某表的两个字段的数据不唯一,需要为这两个字段创建唯一索引的时候,这一切就变得不完美了.列的 ...

  10. SQL Server 2014内存优化表的使用场景(转载)

    最近一个朋友找到走起君,咨询走起君内存优化表如何做高可用的问题 大家知道,内存优化表作为In-Memory OLTP功能是从SQL Server 2014开始引入,用来对抗Oracle 12C的In- ...

随机推荐

  1. Chrome中无法断点调试的解决方案

    chrome的调试功能实在是太强大了,相比之下ie的就是一垃圾. 最近在调试时出现一种情况,死活不能设置断点,也不能跟踪调试,这下抓狂了. JS也是非常简单的,也没有压缩.为什么就不能调试呢? 网上狂 ...

  2. maven 技巧

    M2Eclipse Releases maven eclipse插件最新安装地址 Full Version Tag 1.0 2011-06-22 http://download.eclipse.org ...

  3. 启动MySQL报错

    安装完MySQL,启动MySQL报错,报错信息如下:Starting MySQL....The server quit without updating PID file (/data/mysqlda ...

  4. JMeter 配置元件之计数器Counter

    配置元件之计数器Counter   by:授客 QQ:1033553122 测试环境 apache-jmeter-2.13 1.   计数器简介 允许用户创建一个在线程组范围之内都可以被引用的计数器. ...

  5. Expo大作战(三十五)--expo sdk api之Location!

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...

  6. 【第五篇】SAP ABAP7.5x新语法之命名规约

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:SAP ABAP7.5x系列之命名规约   命名 ...

  7. mysql 临时数据突然变大

    晚上收到紧报警,一台数据库服务器磁盘空间使用快速从50%使用率到80%.我们生产的数据库都磁盘是>2T 登录机器发现*.myd文件异常大 登入数据库查询进程 mysql>showproce ...

  8. centos6.9设置桥接网络模式方法

    第一步:设置 VMware 在 VMware 中打开[编辑]->[虚拟网络编辑器],添加 VMnet0,并选择桥接模式.需要注意的是,需要选择“桥接到”的网卡,使用无线网卡就选无线网卡,使用有线 ...

  9. [Mac] How do I move a window whose title bar is off-screen?

    有时窗口一不小心拖出视野外了,此时无法移动窗口.如何还原? 有人遇到相似问题,已有解决方法: 方法就是,菜单 Windows - Zoom  这时窗口会还原.

  10. Python3编写网络爬虫05-基本解析库XPath的使用

    一.XPath 全称 XML Path Language 是一门在XML文档中 查找信息的语言 最初是用来搜寻XML文档的 但是它同样适用于HTML文档的搜索 XPath 的选择功能十分强大,它提供了 ...