c盘越来越大怎么清理?清理C:\Windows\System32\DriverStore\FileRepository_xl_leroy的博客-CSDN博客_filerepository文件夹可以删除吗

mikel阅读(1596)

来源: c盘越来越大怎么清理?清理C:\Windows\System32\DriverStore\FileRepository_xl_leroy的博客-CSDN博客_filerepository文件夹可以删除吗

c盘越来越大怎么清理?
装系统时划了50G给C盘,随着使用C盘的空间越来越小,该如何清理呢?

先解决2个基础软件设置造成的,为C盘腾出空间:

1、QQ
更换QQ文件存储路径。QQ设置->文件管理,图示:

 

2、微信桌面版:

完成以上2步,各位可再使用Windos自带的磁盘整理工具,清理一下C盘,比如清空回收站,删除windows.old等等。

这时候如果C盘空间依旧不够用,那就使用“Space seniffer”进行查看各个文件大小了,根据需要进行删除了(千万不能删除系统文件)。

必备工具有两个:
1、Space seniffer;

2、Driver Store Explorer;

Space seniffer是一款可直观查看各磁盘文件大小的软件;

下载、解压后,在“SpaceSniffer.exe”上鼠标右击,选择“管理员身份运行”,等待扫描一会。

 

可见“C:\Windows\System32\DriverStore\FileRepository”占据空间很大;

经过搜索查询可知道这个文件夹存储的是驱动文件。而驱动文件进行更新后,老的驱动文件没有删除,长久下来,就会造成很多老旧无效的驱动文件占据掉很大空间。

网上有很人说“FileRepository”,这个文件夹的内容可以删除;也有人说这个文件夹不能删除。

其实,准确点说,“FileRepository”文件夹中那些老旧不会使用到的文件是可以删除的,正在使用的文件就不能删除,以免会造成系统不稳定的影响。

其实,windows系统有一个“pnputil.exe”工具可以用来清理这个文件夹的,只不过是命令行方式,操作起来不是很方便。(参考资料)

世界上大神还是有的,编写了一个“Driver Store Explorer”使用图形界面来替换命令行的操作方式,Driver Store Explorer下载地址。

“Driver Store Explorer”下载、解压之后,选中“Rapr.exe”右键“管理员身份运行”,等待扫描一会。

如图:

 

“选取旧的驱动”,虽然使用工具选择旧驱动进行删除,但在删除之前我们还是需要查看一下的。

自信看下来就能发现,其实有大多驱动都是几十K,几百K的。也有几百M的,有可能那些几百M的驱动都没有选择。

这时候就需要我自己确认一下那些驱动其实是可以不要的,回到桌面:“我的电脑”右键选择“管理”->“设备管理器”;逐项查看目前那些驱动是在使用的,逐项查看的工作比较累,其实我们只需要抓住那些占据大空间的驱动即可。

可以查看的到我的“显示适配器”驱动有很多个,而且都是好几百M,“选择旧的驱动”功能键没有选中这些包。我就手动查看“显示适配器”的驱动,查看目前使用的驱动是哪一个,然后再选择删除掉那些不使用的驱动即可;

手动查看“显示适配器”驱动的方法:选择某个驱动,右键“属性”->“驱动程序”,就可以查看到当前使用的版本了;

如果选择了当前正在使用的驱动,软件会进行报错的,是无法删除的。

如果删除掉了旧的驱动依然没有解放出来足够的空间给C盘,那就继续使用“Space seniffer”,查看哪些文件占据空间比较大,再酌情进行删除,碰到不清楚的文件还是先查询弄清楚文件是做什么用的,再去删除它,毕竟稳定才是最重要的。
————————————————
版权声明:本文为CSDN博主「xl_leroy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xl_leroy/article/details/88629159

SQL Server 监控统计阻塞脚本信息 - 潇湘隐者 - 博客园

mikel阅读(554)

来源: SQL Server 监控统计阻塞脚本信息 – 潇湘隐者 – 博客园

数据库产生阻塞(Blocking)的本质原因 :SQL语句连续持有锁的时间过长 ,数目过多, 粒度过大。阻塞是事务隔离带来的副作用,它是不可避免的,而且是一个数据库系统常见的现象。 但是阻塞的时间和出现频率要控制在一定的范围内,阻塞持续的时间过长或阻塞出现过多(过于频繁),就会对数据库性能产生严重的影响。

    很多时候,DBA需要知道数据库在出现性能问题时,有没有发生阻塞? 什么时候开始的?发生在那个数据库上? 阻塞发生在那些SQL语句之间? 阻塞的时间有多长? 阻塞发生的频率? 阻塞有关的连接是从那些客户端应用发送来的?…….

    如果我们能够知道这些具体信息,我们就能迅速定位问题,分析阻塞产生的原因,  从而找出出现性能问题的根本原因,并根据具体原因给出相应的解决方案(索引调整、优化SQL语句等)。

    查看阻塞的方法比较多, 我在这篇博客MS SQL 日常维护管理常用脚本(二)里面提到查看阻塞的一些方法:

方法1:查看那个引起阻塞,查看blk不为0的记录,如果存在阻塞进程,则是该阻塞进程的会话 ID。否则该列为零。

    EXEC sp_who active

方法2:查看那个引起阻塞,查看字段BlkBy,这个能够得到比sp_who更多的信息。

    EXEC sp_who2 active

方法3:sp_lock 系统存储过程,报告有关锁的信息,但是不方便定位问题

方法4:sp_who_lock存储过程

方法5:右键服务器-选择“活动和监视器”,查看进程选项。注意“任务状态”字段。

方法6:右键服务名称-选择报表-标准报表-活动-所有正在阻塞的事务。

但是上面方法,例如像sp_who、 sp_who2,sp_who_lock等,都有或多或少的缺点:例如不能查看阻塞和被阻塞的SQL语句。不能从查看一段时间内阻塞发生的情况等;没有显示阻塞的时间……. 我们要实现下面功能:

    1:  查看那个会话阻塞了那个会话

    2:阻塞会话和被阻塞会话正在执行的SQL语句

    3:被阻塞了多长时间

    4:像客户端IP、Proagram_Name之类信息

    5:阻塞发生的时间点

    6:阻塞发生的频率

    7:如果需要,应该通知相关开发人员,DBA不能啥事情都包揽是吧,那不还得累死,总得让开发人员员参与进来优化(有些问题就该他们解决),多了解一些系统运行的具体情况,有利于他们认识问题、解决问题。

    8:需要的时候开启这项功能,不需要关闭这项功能

于是为了满足上述功能,有了下面SQL 语句

SELECT wt.blocking_session_id                  AS BlockingSessesionId
      ,sp.program_name                         AS ProgramName
      ,COALESCE(sp.LOGINAME, sp.nt_username)   AS HostName
      ,ec1.client_net_address                  AS ClientIpAddress
      ,db.name                                 AS DatabaseName
      ,wt.wait_type                            AS WaitType
      ,ec1.connect_time                        AS BlockingStartTime
      ,wt.WAIT_DURATION_MS/1000                AS WaitDuration
      ,ec1.session_id                          AS BlockedSessionId
      ,h1.TEXT                                 AS BlockedSQLText
      ,h2.TEXT                                 AS BlockingSQLText
FROM sys.dm_tran_locks AS tl
INNER JOIN sys.databases db
  ON db.database_id = tl.resource_database_id
INNER JOIN sys.dm_os_waiting_tasks AS wt
  ON tl.lock_owner_address = wt.resource_address
INNER JOIN sys.dm_exec_connections ec1
  ON ec1.session_id = tl.request_session_id
INNER JOIN sys.dm_exec_connections ec2
  ON ec2.session_id = wt.blocking_session_id
LEFT OUTER JOIN master.dbo.sysprocesses sp
  ON SP.spid = wt.blocking_session_id
CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

我们做一个测试例子来验证一下

 

1:打开第一会话窗口1,执行下面语句

USE DBMonitor;

GO

BEGIN TRANSACTION

SELECT * FROM dbo.TEST(TABLOCKX);

--COMMIT TRANSACTION;

2:打开第二个会话窗口2,执行下面语句

USE DBMonitor;
GO
SELECT * FROM dbo.TEST

3:打开第三个会话窗口3,执行下面语句

SELECT wt.blocking_session_id                  AS BlockingSessesionId
      ,sp.program_name                         AS ProgramName
      ,COALESCE(sp.LOGINAME, sp.nt_username)   AS HostName
      ,ec1.client_net_address                  AS ClientIpAddress
      ,db.name                                 AS DatabaseName
      ,wt.wait_type                            AS WaitType
      ,ec1.connect_time                        AS BlockingStartTime
      ,wt.WAIT_DURATION_MS/1000                AS WaitDuration
      ,ec1.session_id                          AS BlockedSessionId
      ,h1.TEXT                                 AS BlockedSQLText
      ,h2.TEXT                                 AS BlockingSQLText
FROM sys.dm_tran_locks AS tl
INNER JOIN sys.databases db
  ON db.database_id = tl.resource_database_id
INNER JOIN sys.dm_os_waiting_tasks AS wt
  ON tl.lock_owner_address = wt.resource_address
INNER JOIN sys.dm_exec_connections ec1
  ON ec1.session_id = tl.request_session_id
INNER JOIN sys.dm_exec_connections ec2
  ON ec2.session_id = wt.blocking_session_id
LEFT OUTER JOIN master.dbo.sysprocesses sp
  ON SP.spid = wt.blocking_session_id
CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

如下图所,我们可以看到阻塞其它会话以及被阻塞会话的信息,如下所示

image

现在上面SQL已经基本实现了查看阻塞具体信息的功能,但是现在又有几个问题:

          1:上面SQL脚本只适合已经出现阻塞情况下查看阻塞信息,如果没有出现阻塞情况,我总不能傻傻的一直在哪里点击执行吧,因为阻塞这种情况有可能在那段时间都不会出现,只会在特定的时间段出现。

          2:我想了解一段时间内数据库出现的阻塞情况,那么需要将阻塞信息保留下来。

         3:有时候忙不过来,我想将这些具体阻塞信息发送给相关开发人员,让他们了解具体情况。

于是我想通过一个存储过程来实现这方面功能,通过设置参数@OutType,默认为输出阻塞会话信息,当参数为”Table” 时,将阻塞信息写入数据库表,如果参数为 “Email”表示将阻塞信息通过邮件发送开发人员。

正好这段时间,我在YourSQLDba上扩展一些功能,于是我将这个存储过程放置在YouSQLDba数据库中。

USE [YourSQLDba]
GO

IF NOT EXISTS(SELECT * FROM sys.objects WHERE object_id=OBJECT_ID(N'[Maint].[BlockingSQLHistory]') AND type='U')
BEGIN
CREATE TABLE Maint.BlockingSQLHistory
(
                RecordTime                        DATETIME           ,
                DatabaseName                      SYSNAME            ,
                BlockingSessesionId               SMALLINT           ,
                ProgramName                       NCHAR(128)         ,
                UserName                          NCHAR(256)         ,
                ClientIpAddress                   VARCHAR(48)        ,
                WaitType                          NCHAR(60)          ,
                BlockingStartTime                 DATETIME           ,
                WaitDuration                      BIGINT             ,
                BlockedSessionId                  INT                ,
                BlockedSQLText                    NVARCHAR(MAX)      ,
                BlockingSQLText                   NVARCHAR(MAX)      ,
                CONSTRAINT PK_BlockingSQLHistory  PRIMARY KEY(RecordTime)
)

END
GO

存储过程如下所示:

USE [YourSQLDba]
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Maint].[sp_who_blocking]') AND type in (N'P', N'PC'))
DROP PROCEDURE [Maint].[sp_who_blocking]
GO





SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO



--==================================================================================================================
--        ProcedureName         :            [Maint].[sp_who_blocking]
--        Author                :            Kerry    http://www.cnblogs.com/kerrycode/
--        CreateDate            :            2014-04-23
--        Description           :            监控数据库阻塞情况,显示阻塞会话信息或收集阻塞会话信息或发送告警邮件
/******************************************************************************************************************
        Parameters                   :                                    参数说明
********************************************************************************************************************
            @OutType         :            默认为输出阻塞会话信息,"Table", "Email"分别表示将阻塞信息写入表或邮件发送
            @EmailSubject    :            邮件主题.默认为Sql Blocking Alert,一般指定,例如“ServerName Sql Blocking Alert"
            @ProfileName     :            @profile_name 默认值为YourSQLDba_EmailProfile
            @RecipientsLst   :            收件人列表
********************************************************************************************************************
   Modified Date    Modified User     Version                 Modified Reason
********************************************************************************************************************
    2014-04-23             Kerry         V01.00.00         新建存储过程[Maint].[sp_who_blocking]

*******************************************************************************************************************/
--==================================================================================================================
CREATE PROCEDURE [Maint].[sp_who_blocking]
(
        @OutType
            VARCHAR(8) ='Default'                  ,
        @EmailSubject
            VARCHAR(120)='Sql Blocking Alert'      ,
        @ProfileName
            sysname='YourSQLDba_EmailProfile'      ,
        @RecipientsLst
             VARCHAR(MAX) = NULL
)
AS
BEGIN

SET NOCOUNT ON;

DECLARE @HtmlContent  NVARCHAR(MAX) ;

    IF @OutType NOT IN ('Default', 'Table','Email')
    BEGIN
        PRINT 'The parameter @OutType is not correct,please check it';

        return;
    END

    IF @OutType ='Default'
        BEGIN

              SELECT db.name                                 AS DatabaseName
                    ,wt.blocking_session_id                  AS BlockingSessesionId
                    ,sp.program_name                         AS ProgramName
                    ,COALESCE(sp.LOGINAME, sp.nt_username)   AS UserName
                    ,ec1.client_net_address                  AS ClientIpAddress
                    ,wt.wait_type                            AS WaitType
                    ,ec1.connect_time                        AS BlockingStartTime
                    ,wt.WAIT_DURATION_MS/1000                AS WaitDuration
                    ,ec1.session_id                          AS BlockedSessionId
                    ,h1.TEXT                                 AS BlockedSQLText
                    ,h2.TEXT                                 AS BlockingSQLText
              FROM sys.dm_tran_locks AS tl
              INNER JOIN sys.databases db
                ON db.database_id = tl.resource_database_id
              INNER JOIN sys.dm_os_waiting_tasks AS wt
                ON tl.lock_owner_address = wt.resource_address
              INNER JOIN sys.dm_exec_connections ec1
                ON ec1.session_id = tl.request_session_id
              INNER JOIN sys.dm_exec_connections ec2
                ON ec2.session_id = wt.blocking_session_id
              LEFT OUTER JOIN master.dbo.sysprocesses sp
                ON SP.spid = wt.blocking_session_id
              CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
              CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2;
         END
     ELSE IF @OutType='Table'
        BEGIN

              INSERT INTO [Maint].[BlockingSQLHistory]
              SELECT GETDATE()                               AS RecordTime
                    ,db.name                                 AS DatabaseName
                    ,wt.blocking_session_id                  AS BlockingSessesionId
                    ,sp.program_name                         AS ProgramName
                    ,COALESCE(sp.LOGINAME, sp.nt_username)   AS UserName
                    ,ec1.client_net_address                  AS ClientIpAddress
                    ,wt.wait_type                            AS WaitType
                    ,ec1.connect_time                        AS BlockingStartTime
                    ,wt.WAIT_DURATION_MS/1000                AS WaitDuration
                    ,ec1.session_id                          AS BlockedSessionId
                    ,h1.TEXT                                 AS BlockedSQLText
                    ,h2.TEXT                                 AS BlockingSQLText
              FROM sys.dm_tran_locks AS tl
              INNER JOIN sys.databases db
                ON db.database_id = tl.resource_database_id
              INNER JOIN sys.dm_os_waiting_tasks AS wt
                ON tl.lock_owner_address = wt.resource_address
              INNER JOIN sys.dm_exec_connections ec1
                ON ec1.session_id = tl.request_session_id
              INNER JOIN sys.dm_exec_connections ec2
                ON ec2.session_id = wt.blocking_session_id
              LEFT OUTER JOIN master.dbo.sysprocesses sp
                ON SP.spid = wt.blocking_session_id
              CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
              CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2;
         END
      ELSE IF @OutType='Email'
         BEGIN

            SET @HtmlContent =
               N'<head>'
             + N'<style type="text/css">h2, body {font-family: Arial, verdana;} table{font-size:11px; border-collapse:collapse;} td{background-color:#F1F1F1; border:1px solid black; padding:3px;} th{background-color:#99CCFF;}</style>'
             + N'<table border="1">'
             + N'<tr>
                 <th>DatabaseName</th>
                 <th>BlockingSessesionId</th>
                 <th>ProgramName</th>
                 <th>UserName</th>
                 <th>ClientIpAddress</th>
                 <th>WaitType</th>
                 <th>BlockingStartTime</th>
                 <th>WaitDuration</th>
                 <th>BlockedSessionId</th>
                 <th>BlockedSQLText</th>
                 <th>BlockingSQLText</th>
                </tr>' +
             CAST (
                    (SELECT db.name                                  AS TD, ''
                           ,wt.blocking_session_id                   AS TD, ''
                           ,sp.program_name                          AS TD, ''
                           ,COALESCE(sp.LOGINAME, sp.nt_username)    AS TD, ''
                           ,ec1.client_net_address                   AS TD, ''
                           ,wt.wait_type                             AS TD, ''
                           ,ec1.connect_time                         AS TD, ''
                           ,wt.WAIT_DURATION_MS/1000                 AS TD, ''
                           ,ec1.session_id                           AS TD, ''
                           ,h1.TEXT                                  AS TD, ''
                           ,h2.TEXT                                  AS TD, ''

                    FROM sys.dm_tran_locks AS tl
                    INNER JOIN sys.databases db
                            ON db.database_id = tl.resource_database_id
                    INNER JOIN sys.dm_os_waiting_tasks AS wt
                            ON tl.lock_owner_address = wt.resource_address
                    INNER JOIN sys.dm_exec_connections ec1
                            ON ec1.session_id = tl.request_session_id
                    INNER JOIN sys.dm_exec_connections ec2
                            ON ec2.session_id = wt.blocking_session_id
                    LEFT OUTER JOIN master.dbo.sysprocesses sp
                            ON SP.spid = wt.blocking_session_id
                    CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
                    CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

               FOR XML PATH('tr'), TYPE
                ) AS NVARCHAR(MAX) ) +
                N'</table>'


                IF @HtmlContent  IS NOT NULL

                BEGIN

                    EXEC msdb.dbo.sp_send_dbmail
                            @profile_name = @ProfileName    ,
                            @recipients   = @RecipientsLst    ,
                            @subject      = @EmailSubject    ,
                            @body         = @HtmlContent    ,
                            @body_format  = 'HTML' ;

                END
        END

END
GO

最后在数据库新建一个作业,调用该存储过程,然后在某段时间启用作业监控数据库的阻塞情况,作业的执行频率是个比较难以定夺的头痛问题,具体要根据系统情况来决定,我习惯2分钟执行一次。

最后,这个脚本还有一个问题,如果阻塞或被阻塞的SQL语句是某个存储过程里面的一段脚本,显示的SQL是整个存储过程,而不是正在执行的SQL语句,目前还没有想到好的方法解决这个问题。我目前手工去查看阻塞情况,如果非要查看存储过程里面被阻塞的正在执行的SQL,一般结合下面SQL语句查看(输入阻塞或被阻塞会话ID替代@sessionid)

SELECT   [Spid] = er.session_id
        ,[ecid]
        ,[Database] = DB_NAME(sp.dbid)
        ,[Start_Time]
        ,[SessionRunTime]    = datediff(SECOND, start_time,getdate())
        ,[SqlRunTime]=     RIGHT(convert(varchar,
                                 dateadd(ms, datediff(ms, sp.last_batch, getdate()), '1900-01-01'),
                            121), 12)
        ,[HostName]
        ,[Users]=COALESCE(sp.LOGINAME, sp.nt_username)
        ,[Status] = er.status
        ,[WaitType] = er.wait_type
        ,[Waitime] = er.wait_time/1000
        ,[Individual Query] = SUBSTRING(qt.text, er.statement_start_offset / 2,
                                       ( CASE WHEN er.statement_end_offset = -1
                                              THEN LEN(CONVERT(NVARCHAR(MAX), qt.text))
                                                   * 2
                                              ELSE er.statement_end_offset
                                         END - er.statement_start_offset ) / 2)
        ,[Parent Query] = qt.text
        ,[PROGRAM_NAME] = program_name
FROM    sys.dm_exec_requests er
        INNER JOIN sys.sysprocesses sp ON er.session_id = sp.spid
        CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) AS qt
WHERE   session_Id = @sessionid;

为什么数据库有时候不能定位阻塞(Blocker)源头的SQL语句 - 潇湘隐者 - 博客园

mikel阅读(650)

来源: 为什么数据库有时候不能定位阻塞(Blocker)源头的SQL语句 – 潇湘隐者 – 博客园

   在SQL Server数据库或OACLE数据库当中,通常一个会话持有某个资源的锁,而另一个会话在请求这个资源,就会出现阻塞(blocking)。这是DBA经常会遇到的情况。当出现SQL语句的阻塞时,很多人想查看阻塞的源头(哪个SQL语句阻塞了哪个SQL),这样方便直观、简洁明了的定位问题。但是很多时候,很多场景,我们通过SQL语句并不能或者说不容易定位到阻塞者(Blocker)的SQL语句,当然我们可以很容易找到被阻塞的SQL语句,以及它在等待的锁资源。下面我们先分析一下SQL Server数据库的这类场景,然后分析一下ORACLE数据库的这类场景。如有不足的地方,敬请指出。

 

在SQL Server当中,我们先准备下面测试环境(测试用的表和数据)。

 
USE Test;
GO
CREATE TABLE Test 
(
    ID        INT ,
    NAME    VARCHAR(12)
);
 
 
INSERT INTO Test
VALUES (1000, 'Kerry');
 
INSERT INTO Test
VALUES(1001, 'Jimmy');

 

场景1:我们构造这样一个简单的场景,例如如下:

 

在会话81中执行下面SQL语句

BEGIN TRAN

    UPDATE Test SET NAME='Tina' WHERE ID=1000;

 

 

在会话72中执行下面SQL语句

SELECT * FROM TEST;

 

 

在另外一个会话窗口执行下面语句,查看阻塞(blocker)者和被阻塞者的SQL语句(这里能够定位到阻塞者(blocker)的SQL语句)。如下所示

SELECT wt.blocking_session_id                    AS BlockingSessesionId
      ,sp.program_name                           AS Blocking_ProgramName
      ,COALESCE(sp.LOGINAME, sp.nt_username)     AS Blocking_HostName
      ,ec1.client_net_address                    AS ClientIpAddress
      ,db.name                                   AS DatabaseName
      ,wt.wait_type                              AS WaitType
      ,ec1.connect_time                          AS BlockingStartTime
      ,wt.WAIT_DURATION_MS/1000                  AS WaitDuration
      ,ec1.session_id                            AS BlockedSessionId
      ,h1.TEXT                                   AS BlockedSQLText
      ,h2.TEXT                                   AS BlockingSQLText
FROM sys.dm_tran_locks  AS tl WITH(NOLOCK)
INNER JOIN sys.databases AS db  WITH(NOLOCK)
  ON db.database_id = tl.resource_database_id
INNER JOIN sys.dm_os_waiting_tasks AS wt  WITH(NOLOCK)
  ON tl.lock_owner_address = wt.resource_address
INNER JOIN sys.dm_exec_connections  ec1 WITH(NOLOCK)
  ON ec1.session_id = tl.request_session_id
INNER JOIN sys.dm_exec_connections  ec2 WITH(NOLOCK)
  ON ec2.session_id = wt.blocking_session_id
LEFT OUTER JOIN master.dbo.sysprocesses AS sp WITH(NOLOCK)
  ON SP.spid = wt.blocking_session_id
CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

clip_image001

 

但是这个场景是一个非常理想化的场景,实际场景中,可能会话81接下来会去执行其它SQL语句,它并不会一直停留在这个SQL语句上,例如,我们在会话81中执行SELECT GETDATE();这个SQL语句

BEGIN TRAN

 UPDATE Test SET NAME='Tina' WHERE ID=1000;

 SELECT GETDATE();

clip_image002

 

如上所示,此时查到的Blocker者的SQL语句为”SELECT GETDATE();”, 而这个SQL其实和被阻塞的SQL没有半毛关系。即使使用sp_WhoIsActive这样专业的SQL亦是如此。

clip_image003

 

当然我们可以查看其等待的锁对象信息,这也是我们所能追踪、捕获的。如下所示:

<Database name="Test">
  <Locks>
    <Lock request_mode="S" request_status="GRANT" request_count="1" />
  </Locks>
  <Objects>
    <Object name="Test" schema_name="dbo">
      <Locks>
        <Lock resource_type="OBJECT" request_mode="IS" request_status="GRANT" request_count="1" />
        <Lock resource_type="PAGE" page_type="*" request_mode="IS" request_status="GRANT" request_count="1" />
        <Lock resource_type="RID" page_type="*" request_mode="S" request_status="WAIT" request_count="1" />
      </Locks>
    </Object>
  </Objects>
</Database>

clip_image004

 

这种场景,如果只是某个会话发出的即席查询,那么你几乎已经很难捕获到阻塞的源头UPDATE Test SET NAME=’Tina’ WHERE ID=1000这个SQL语句了。除非你结合其它一些手段,逆向推断。

 

场景2:上面查找SQL阻塞的SQL语句,有时候只能定位到某一个存储过程或一大段即席查询SQL。

 

例如,下面一个构造的存储过程,一个用户正在一个会话当中执行它,

CREATE PROCEDURE PRC_TEST
AS
BEGIN

    BEGIN TRAN TR1

    UPDATE Test SET NAME='YourName' WHERE ID=1000;

    SELECT * FROM sys.sysprocesses WHERE spid=@@SPID;

    WAITFOR DELAY '00:00:20';

    COMMIT TRAN TR1;

END
GO

 

另外一个用户在另外一个会话执行下面查询SQL语句


SELECT * FROM TEST;

 

查看阻塞的历史记录

clip_image005

 

你会看到捕获的是整个存储过程,当然这个测试案例很容易知道是那个SQL语句阻塞了,实际的存储过程可能业务很复杂,SQL语句也非常多,你想从一个存储过程里面找到阻塞者(Blocker)的SQL语句其实是非常麻烦的。需要你仔细甄别,当存储过程的业务逻辑复杂,SQL语句非常多时,这是一个头痛的事情

其实遇到这些场景,我们大可不必一定要查看阻塞这(Blocker)的具体SQL,我们只需要查看被阻塞者,等待的锁对象资源的相关信息即可,你可以大致判断到底是一个什么类型的SQL导致了这类阻塞。

 

 

那么我们接下来看看ORACLE数据库场景吧。我们先准备一个测试环境(测试表和相关数据)

 

CREATE TABLE "TEST"."TEST"
(    "ID" NUMBER,
    "NAME" VARCHAR2(12)
);

INSERT INTO TEST
SELECT 1001, 'jimmy' FROM DUAL UNION ALL
SELECT 1002, 'Kerry' FROM DUAL;

COMMIT;

 

 

接下来我们在会话窗口一执行下面SQL:

[oracle@DB-Server ~]$ sqlplus test/test

SQL*Plus: Release 11.2.0.1.0 Production on Tue Aug 30 10:16:43 2016

Copyright (c) 1982, 2009, Oracle.  All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL> show user;
USER is "TEST"
SQL> UPDATE TEST SET NAME='KKK' WHERE ID =1001;

1 row updated.

SQL>

 

在另外一个会话窗口二执行下面SQL

[oracle@DB-Server ~]$ sqlplus test/test

SQL*Plus: Release 11.2.0.1.0 Production on Tue Aug 30 10:17:22 2016

Copyright (c) 1982, 2009, Oracle.  All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL> show user;
USER is "TEST"
SQL> UPDATE TEST SET NAME='Ken' WHERE ID =1001;

 

然后我们在第三个窗口执行下面SQL语句,查看阻塞和被阻塞的SQL语句

SELECT dba_objects.object_name,
       locks_t.row#,
       locks_t.blocked_secs,
       locks_t.blocker_text,
       locks_t.blocked_text,
       locks_t.blocked_sql_text,
       locks_t.blocking_sql_text
  FROM (SELECT /*+ NO_MERGE */
               blocking_lock_session.username||'@'||blocking_lock_session.machine||'(SID='||blocking_lock_session.sid||') ['||
               blocking_lock_session.program||'/PID='||blocking_lock_session.process||']' as blocker_text,
               blocked_lock_session.username||'@'||blocked_lock_session.machine|| '(SID='||blocked_lock_session.sid||') ['||
               blocked_lock_session.program||'/PID='||blocked_lock_session.process||']' as blocked_text,
               blocked_lock_session.row_wait_obj#,
               blocked_lock_session.row_wait_file#,
               blocked_lock_session.row_wait_block#,
               blocked_lock_session.row_wait_row#,
               DBMS_ROWID.ROWID_CREATE (1,
                  blocked_lock_session.row_wait_obj#,
                  blocked_lock_session.row_wait_file#,
                  blocked_lock_session.row_wait_block#,
                  blocked_lock_session.row_wait_row#) row#,
               blocked_lock_session.seconds_in_wait blocked_secs,
               blocked_sql.sql_text blocked_sql_text,
               blocking_sql.sql_text blocking_sql_text
          FROM v$lock blocking_lock,
               v$session blocking_lock_session,
               v$lock blocked_lock,
               v$session blocked_lock_session,
               v$sql blocked_sql,
               v$sql blocking_sql
         WHERE blocking_lock.block = 1
           AND blocking_lock.id1 = blocked_lock.id1
           AND blocking_lock.id2 = blocked_lock.id2
           AND blocked_lock.request > 0
           AND blocking_lock.sid = blocking_lock_session.sid
           AND blocked_lock.sid = blocked_lock_session.sid
           AND blocked_lock_session.sql_id = blocked_sql.sql_id
           AND blocked_lock_session.sql_child_number = blocked_sql.child_number
           AND blocking_lock_session.PREV_SQL_ADDR(+)  =blocking_sql.ADDRESS
       ) locks_t,
       dba_objects
 WHERE locks_t.row_wait_obj# = dba_objects.object_id
ORDER BY locks_t.blocked_secs;

clip_image006

 

如果我们在会话窗口1,再执行一个语句,如下所示

 

SQL> show user;
USER is "TEST"
SQL> UPDATE TEST SET NAME='KKK' WHERE ID =1001;

1 row updated.

SQL> select * from dual;

D
-
X

 

此时捕获到的是select * from dual; 这个SQL跟被阻塞的SQL没有任何关系,当然如果你继续在会话窗口执行其它SQL语句,捕获的都是不相关的SQL语句,已经没有任何意义

clip_image007

 

出现这个问题,是因为当一个会话正在执行某个SQL语句,那么v$session视图中的SQL_ID记录的是正在执行SQL的SQL_ID,当会话空闲或执行其它SQL语句后,SQL_ID就会变化,PRE_SQL_ID记录上一个执行完的SQL的SQL_ID值,PREV_SQL_ADDR也是如此。如下英文所述 :

 

According to the Reference Manual entry for V$SESSION the SQL_ID column represents the current SQL statement being executed by a session.  If the session is idle there is no current SQL statement.  Also if a session performs an update then performs a query the SQL_ID would reflect the query and not the update which is the statement that is blocking.  There is in fact no query that is guaranteed to find the blocking SQL.  Unless the blocking statement is the current statement all you can find for sure I the blocking session

 

如果你不用SQL*Plus,使用PL/SQL Developer这个工具,你会看到BLOCKING_SQL_TEST永远都是begin sys.dbms_output.get_line(line => :line, status => :status); end; 这个是因为PL/SQL Developer在执行完SQL后,会调用其它SQL语句,当然SQL Developer不会有这样的问题。

 

所以综上述,想要找到阻塞的源头SQL语句,只用SQL查询,其实在很多场景是不太现实的,所以很多SQL语句都只给出阻塞者的会话信息或锁定对象信息。如下所示

 

会话ID为8的会话执行下面SQL

UPDATE TEST SET NAME='TEST' WHERE ID=1001;

 

会话ID为137的会话执行下面SQL

UPDATE TEST SET NAME='TES1' WHERE ID=1001;

然后我们使用get_locked_objects_rpt.sql查看被阻塞的SQL,以及锁定相关对象的信息(get_locked_objects_rpt.sql请参考get_locked_objects_rpt.sql

SQL> @get_locked_objects_rpt.sql
Enter value for 1: 6
old  42:    AND locks_t.blocked_secs > &1
new  42:    AND locks_t.blocked_secs > 6
========= $Revision: 1.4 $ ($Date: 2013/09/16 13:15:22 $) ===========
Locked object : TEST
Locked row#   : AAASEkAAEAAAADVAAA
Blocked for   : 19 seconds
Blocker info. : TEST@GFG1\GET253194(SID=8) [plsqldev.exe/PID=17988:14616]
Blocked info. : TEST@get253194(SID=137) [SQL Developer/PID=17780]
Blocked SQL   : UPDATE TEST SET NAME='TES1' WHERE ID=1001
Found 1 blocked session(s).
Disconnected from Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
[oracle@DB-Server ~]$

clip_image008

然后我通过上面的Locked Object知道被锁定的对象为Test表的ROWID为AAASRCAAEAAAADVAAA的记录,如下所示

clip_image009

 

 

SQL Server中SELECT会真的阻塞SELECT吗? - 潇湘隐者 - 博客园

mikel阅读(690)

来源: SQL Server中SELECT会真的阻塞SELECT吗? – 潇湘隐者 – 博客园

SQL Server中,我们知道一个SELECT语句执行过程中只会申请一些意向共享锁(IS) 与共享锁(S), 例如我使用SQL Profile跟踪会话86执行SELECT * FROM dbo.TEST WHERE OBJECT_ID =1 这个查询语句,其申请、释放的锁资源的过程如下所示:

而且从最常见的锁模式的兼容性表,我们可以看到IS锁与S锁都是兼容的,也就是说SELECT查询是不会阻塞SELECT查询的。

现有的授权模式
请求的模式 IS S U IX SIX X
意向共享 (IS)
共享 (S)
更新 (U)
意向排他 (IX)
意向排他共享 (SIX)
排他 (X)

但是在某些特殊场景。你会看到SELECT语句居然“阻塞”SELECT操作,那么SQL Server中SELECT会真的阻塞SELECT操作吗?我们先构造测试的案例场景,那么先准备测试数据吧

CREATE TABLE TEST (OBJECT_ID  INT, NAME VARCHAR(8)); CREATE INDEX PK_TEST ON TEST(OBJECT_ID)   DECLARE @Index INT =0; WHILE @Index < 20BEGIN    INSERT INTO TEST    SELECT @Index, ‘kerry’;        SET @Index = @Index +1;END

在会话窗口A中,执行下面SQL语句,模拟一个UPDATE语句正在执行

 BEGIN TRANSACTION     UPDATE dbo.TEST SET NAME=’Kerry’ WHERE OBJECT_ID=1;    –ROLLBACK;

会话窗口B中,执行下面的SQL语句

SELECT * FROM dbo.TEST WHERE OBJECT_ID=1

会话窗口C中,执行下面的SQL语句

SELECT * FROM dbo.TEST WHERE OBJECT_ID=1

我实验的场景下,会话窗口A的会话ID为85,会话窗口B的会话ID为90,会话窗口C的会话ID为87,如下所示

如下所示,你会看到SELECT语句“阻塞”了SELECT语句,即会话90“阻塞”了会话87, 它们的等待事件都为LCK_M_S,也就是说它们都在等待获取共享锁,也许你会置疑这个SQL是否有问题,那么我们使用SP_WHO来查看,你会发现也是如此,如下所示:

如下所示,我们会发现会话ID为90 、87的会话都在等待类型为RID,Resource为1:24171:1的共享锁

其实应该说,会话87、90都在等待RID对象的共享锁,我们知道共享锁与意向共享锁都是兼容的,所以SELECT是不会阻塞SELECT的,那么又怎么解释这个现象呢?在宋大神的指点下,粗略的翻了Database System Implementaion这本书(很多原理性知识,看起来相当吃力)。里面介绍了在锁表(lock table)以及Element Info、Handling Lock Requests、Handling Unlocks等概念,有一个有意思的图所示,

在锁表(lock table)里,elements info里的锁的申请是在一个类似队列的结构。先进先出机制,所以当会话90先进入队列,它在等待共享锁(S), 会话87也进入队列等待共享锁(S),而且它在会话90的后面(即会话90这个elements info后面的Next指针指向会话87会话的事务),由于两个会话都被阻塞,这两个会话的Wait字段都是Yes,由于内部某些机制,会话87显示阻塞它的会话为90(这个是我个人臆测,实际具体原因有待考究),实质阻塞的源头还是会话85. 当会话85释放排它锁(X)后,会话队列根据下面几个原则来处理解锁(Handling Unlocks):

1: First-come-first-served: Grant the lock request that has been waiting the longest. This strategy guarantees no starvation, the situation where a transaction can wait forever for a lock

先来先服务(队列的原则):授予锁等待时间最长的锁请求,这种策略保证不会饿死(翻译感觉不贴切),即一个事务不会永远等待锁的情况。

  1. Priority to shared locks: First grant all the shared locks waiting. Then,grant one update lock, if there are any waiting. Only grant an exclusivelock if no others are waiting. This strategy can allow starvation, if atransaction is waiting for a U or X lock.

共享锁优先,首先授予所有等待共享锁(S),然后授予其中一个更新锁(U),如果有其它类型等待,只有在没有其它锁等待时,才授予排它锁、这一策略允许等待更新锁或排它锁的事务饿死(结束)

  1. Priority to upgrading: If there is a transaction with a U lock waiting toupgrade it to an X lock, grant that first. Otherwise, follow one of theother strategies mentioned.

锁升级优先,如果有一个持有共享锁(U)等待升级Wie排他锁(X),那么先授予它排它锁,否则采用前面已经提到的策略中的一个。

按照这些原则,当会话85释放了排它锁(X)后,调度器(Scheduler)应该会根据先后顺序依次授予会话90、87共享锁(S),两者的阻塞会几乎同时消失。 这个可以也可以通过实验进行一个大概的推断, 在上面实验中,你可以手工取消90会话的查询操作,然后再查看阻塞情况,就会发现会话87被85阻塞了。这个阻塞的源头就变成了85,而不是90了。

PS:上面是个人结合一些知识和理解,做的一些肤浅的判断与分析,如果不对的地方,敬请指正!

参考资料:

Database System Implementaion

MS SQL 日常维护管理常用脚本(二) - 潇湘隐者 - 博客园

mikel阅读(630)

来源: MS SQL 日常维护管理常用脚本(二) – 潇湘隐者 – 博客园

监控数据库运行

下面是整理、收集监控数据库运行的一些常用脚本,也是MS SQL 日常维护管理常用脚本(一)的续集,欢迎大家补充、提意见。

 

查看数据库登录名信息

Code Snippet
  1. SELECT name                                AS LoginName ,
  2.        dbname                              AS DefaultDB ,
  3.        createdate                          AS CreateDate,
  4.        updatedate                          AS UpdateDate,
  5.        language                            AS Language  ,
  6.        CASE WHEN isntname = 1 THEN ‘NT USER’
  7.            ELSE ‘SQL USER’    END          AS UserType
  8. FROM syslogins;

 

查看数据库用户信息

SELECT * FROM sysusers;

查看用户拥有的服务器角色
 

方法1: 用SSMS管理工具查看

方法2: 脚本查询


查看用户角色
  1. SELECT name            ,
  2.        CASE WHEN sysadmin     = 1       THEN ‘yes’     ELSE ” END AS IsSysadmin        ,
  3.        CASE WHEN dbcreator    = 1       THEN ‘yes’     ELSE ” END AS IsDbCreate        ,
  4.        CASE WHEN securityadmin= 1       THEN ‘yes’     ELSE ” END AS IsSecurityadmin   ,
  5.        CASE WHEN bulkadmin    = 1       THEN ‘yes’     ELSE ” END AS IsBulkadmin       ,
  6.        CASE WHEN diskadmin    = 1       THEN ‘yes’     ELSE ” END AS IsDiskadmin       ,
  7.        CASE WHEN processadmin = 1       THEN ‘yes’     ELSE ” END AS IsProcessadmin    ,
  8.        CASE WHEN serveradmin  = 1       THEN ‘yes’     ELSE ” END AS IsServeradmin     ,
  9.        CASE WHEN setupadmin   = 1       THEN ‘yes’     ELSE ” END AS IsSetupadmin
  10. FROM syslogins
  11. –WHERE NAME=’loginname’

查看最大工作线程数

Code Snippet
  1. SELECT  max_workers_count
  2.   FROM  sys.dm_os_sys_info

 

查看当前用户进程的会话ID

 

SELECT @@SPID

 

查询当前会话使用哪种协议

Code Snippet
  1. SELECT net_transport
  2. FROM   sys.dm_exec_connections
  3. WHERE session_id = @@SPID;

查看当前连接的会话信息

–进程号1–50是SQL Server系统内部用的

SELECT * FROM sys.dm_exec_sessions WHERE session_id >=51

–查看某台机器的连接会话信息

SELECT * FROM sys.dm_exec_sessions WHERE session_id >=51 AND host_name='PO130018801'

–查看某个登录名的连接会话信息

SELECT * FROM sys.dm_exec_sessions WHERE session_id >=51 AND login_name='username'

–查看活动的连接会话信息

SELECT * FROM sys.dm_exec_sessions WITH(NOWAIT) WHERE session_id >=51 AND status ='running'

–查找连接到服务器的用户并返回每个用户的会话数

SELECT  login_name ,
        COUNT(session_id) AS session_count
FROM    sys.dm_exec_sessions
GROUP BY login_name ;
查看正在执行的SQL语句

方法1: 选择数据库实例,单击右键,选择”活动监视器“,监控/查看正在执行的SQL

方法2: profile去跟踪,比较耗费资源。

 

方法3:


Code Snippet
  1. SELECT[Spid] = session_Id ,
  2.       ecid ,
  3.       [Database] = DB_NAME(sp.dbid) ,
  4.       [User] = nt_username ,
  5.       [Status] = er.status ,
  6.       [Wait] = wait_type ,
  7.       [Individual Query] = SUBSTRING(qt.text, er.statement_start_offset / 2,
  8.                                      ( CASE WHEN er.statement_end_offset = -1
  9.                                             THEN LEN(CONVERT(NVARCHAR(MAX), qt.text))
  10.                                                  * 2
  11.                                             ELSE er.statement_end_offset
  12.                                        END – er.statement_start_offset ) / 2) ,
  13.       [Parent Query] = qt.text ,
  14.       Program = program_name ,
  15.       Hostname ,
  16.       nt_domain ,
  17.       start_time
  18.   FROMsys.dm_exec_requests er
  19.       INNER JOIN sys.sysprocesses sp ON er.session_id = sp.spid
  20.       CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) AS qt
  21.  WHEREsession_Id >= 51

方法4:

Code Snippet
  1. SELECT   m.session_id ,
  2.          m.start_time ,
  3.          m.command    ,
  4.          m.wait_type  ,
  5.          m.cpu_time   ,
  6.         CAST(s.text AS VARCHAR(1000)) AS sqlText
  7.  FROMmaster.sys.dm_exec_requests m WITH ( NOLOCK )
  8.      CROSS APPLY fn_get_sql(m.sql_handle) s
  9. SELECT  r.session_id,
  10.         r.start_time      ,
  11.         r.command         ,
  12.         r.wait_type       ,
  13.         r.cpu_time        ,
  14.         s.text
  15.  FROMsys.dm_exec_requests r
  16.      CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) s

–查看某个会话ID正在执行的SQL

Code Snippet
  1. SELECT  m.session_id ,
  2.         m.start_time ,
  3.         m.command    ,
  4.         m.wait_type  ,
  5.         m.cpu_time   ,
  6.       CAST(s.text AS VARCHAR(1000)) AS sqlText
  7.   FROMmaster.sys.dm_exec_requests m WITH ( NOLOCK )
  8.       CROSS APPLY fn_get_sql(m.sql_handle) s
  9.  WHEREm.session_id = 342
  10.     SELECT   r.session_id      ,
  11.              r.start_time      ,
  12.              r.command         ,
  13.              r.wait_type       ,
  14.              r.cpu_time        ,
  15.              s.text
  16.   FROM sys.dm_exec_requests r
  17.       CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) s
  18. WHERE r.seesion_id =342

查看SQL SERVER进程执行的语句

Code Snippet
  1. USE master
  2. DECLARE @spid INT ;
  3. DECLARE @sql_handle BINARY(20) ;
  4. SET @spid = 56
  5.   SELECT@sql_handle = sql_handle
  6.     FROMsysprocesses AS A WITH ( NOLOCK )
  7.    WHEREspid = @spid ;
  8.   SELECTtext
  9.     FROM::fn_get_sql(@sql_handle) ;

查找TOP N语句

按平均 CPU 时间返回排名前十个的查询的相关信息。此示例将根据查询的查询哈希对查询进行聚合,以便按照查询的累积资源消耗来分组在逻辑上等效的查询。
–注意:SQL 2005 某些版本,没有sys.dm_exec_query_stats系统动态视图没有query_hash视图。

Code Snippet
  1. USE DBNAME;
  2. GO
  3. SELECT TOP 10 query_stats.query_hash             AS “Query Hash”,
  4.     SUM(query_stats.total_worker_time) /
  5.     SUM(query_stats.execution_count)             AS “Avg CPU Time”,
  6.     MIN(query_stats.statement_text)              AS “Statement Text”
  7. FROM
  8.     (SELECT QS.*,
  9.     SUBSTRING(ST.text,(QS.statement_start_offset/2) + 1,
  10.     ((CASE statement_end_offset
  11.         WHEN -1 THEN DATALENGTH(st.text)
  12.         ELSE QS.statement_end_offset END
  13.             – QS.statement_start_offset)/2) + 1) AS statement_text
  14.      FROM sys.dm_exec_query_stats AS QS
  15.      CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) as ST) as query_stats
  16. GROUP BY query_stats.query_hash
  17. ORDER BY 2 DESC;
  18. GO
查看会话阻塞/死锁信息
 

方法1:查看那个引起阻塞,查看blk不为0的记录,如果存在阻塞进程,则是该阻塞进程的会话 ID。否则该列为零。 

EXEC sp_who active

方法2:查看那个引起阻塞,查看字段BlkBy,这个能够得到比sp_who更多的信息。

EXEC sp_who2 active

方法3:sp_lock 系统存储过程,报告有关锁的信息,但是不方便定位问题

方法4:sp_who_lock存储过程

Code Snippet
  1. USE master;
  2. GO
  3. SET ANSI_NULLS ON;
  4. GO
  5. SET QUOTED_IDENTIFIER ON;
  6. GO
  7. IF EXISTS(SELECT 1 FROM sysobjects WHERE id=OBJECT_ID(N’sp_who_lock’)
  8.                                        AND OBJECTPROPERTY(id, ‘IsProcedure’) =1)
  9.     DROP PROCEDURE sp_who_lock;
  10. GO
  11. –==================================================================================================
  12. —            ProcedureName             :            sp_who_lock
  13. —            Author                    :            作者不详,出自网络
  14. —            CreateDate                :            2013-05-13
  15. —            Description               :            查看阻塞和死锁信息
  16. /**************************************************************************************************
  17.         Parameters                    :              参数说明
  18. ***************************************************************************************************
  19.                                                      无参存储过程
  20. ***************************************************************************************************
  21.         Modified Date            Modified User     Version                 Modified Reason
  22. ***************************************************************************************************
  23.        2013-06-03                    Kerry        V01.00.01 调整存储过程格式,代码部分修改以及增加注释信息
  24. ***************************************************************************************************/
  25. –=================================================================================================
  26.   CREATE PROCEDURE sp_who_lock
  27.     AS
  28. BEGIN
  29. DECLARE @spid                            INT;
  30. DECLARE @block                           INT;
  31. DECLARE @RowCount                        INT;
  32. DECLARE @RowIndex                        INT;
  33. –创建临时表,保持被阻塞或正阻塞其他SQL的SQL语句信息
  34. CREATE TABLE #tmp_lock_who
  35. (
  36.       id    INT IDENTITY(1, 1) ,
  37.       spid    SMALLINT ,
  38.       block SMALLINT
  39. )
  40. IF @@ERROR<>0 RETURN @@ERROR;
  41. INSERT INTO #tmp_lock_who
  42.         (
  43.           spid ,
  44.           block
  45.         )
  46.         SELECT  0 ,
  47.                 blocked
  48.         FROM    ( SELECT    *
  49.                   FROM      sysprocesses
  50.                   WHERE     blocked > 0
  51.                 ) a
  52.         WHERE   NOT EXISTS( SELECT *
  53.                              FROM   ( SELECT    *
  54.                                       FROM      sysprocesses
  55.                                       WHERE     blocked > 0
  56.                                     ) b
  57.                              WHERE  a.blocked = spid )
  58.         UNION
  59.         SELECT  spid ,
  60.                 blocked
  61.         FROM    sysprocesses
  62.         WHERE   blocked > 0;
  63. IF @@ERROR<>0 RETURN @@ERROR;
  64. — 找到临时表的记录数
  65.   SELECT@RowCount = COUNT(1) ,
  66.         @RowIndex = 1
  67.     FROM#tmp_lock_who
  68. IF @@ERROR<>0 RETURN @@ERROR;
  69.   IF@RowCount=0
  70.   SELECT  N’现在没有阻塞和死锁信息’ AS MESSAGE;
  71. — — 循环开始
  72. WHILE @RowIndex <= @RowCount
  73. BEGIN
  74.     — 取第一条记录
  75.     SELECT     @spid    = spid,
  76.                @block   = block
  77.     FROM #tmp_lock_who
  78.     WHERE Id = @RowIndex
  79.     IF @spid = 0
  80.         SELECT N’引起数据库死锁的是: ‘ + CAST(@block AS VARCHAR(10))
  81.             +  N’进程号,其执行的SQL语法如下’;
  82.     ELSE
  83.         SELECT N’进程号SPID:’ + CAST(@spid AS VARCHAR(10)) + N’被进程号SPID:’
  84.             +  CAST(@block AS VARCHAR(10)) +’阻塞,其当前进程执行的SQL语法如下’;
  85.     DBCC INPUTBUFFER(@block )
  86.     SET @RowIndex = @RowIndex + 1;
  87. END;
  88.     DROP TABLE #tmp_lock_who;
  89.     RETURN 0;
  90. END

方法5:右键服务器-选择“活动和监视器”,查看进程选项。注意“任务状态”字段。

方法6:右键服务名称-选择报表-标准报表-活动-所有正在阻塞的事务。

小结:总结之后,才发现居然有这么多方法,MGD,系统的整理、梳理知识点是非常有必要的,你能更全面、深入的了解。


查看内存状态

dbcc memorystatus

具体如何分析,请查看官方文档http://support.microsoft.com/kb/907877/zh-cn


查看脚本执行时间

方法1: 查看SSMS管理器,查询窗口右下角
方法2:

Code Snippet
  1. DECLARE @exectime DATETIME
  2.   SELECT@exectime = GETDATE()
  3. –SQL 语句
  4. PRINT N’SQL执行耗时:’ + CONVERT(VARCHAR(10), DATEDIFF(ms, @exectime, GETDATE()))

方法3:

SET STATISTICS TIME ON

–SQL 语句

 

查看进程正在执行的SQL语句 

dbcc inputbuffer ()

查看那些表缺少索引 

 

下面语句功能强大,执行结果受统计信息的影响

Code Snippet
  1. SELECT sys.objects.name table_name,
  2.        mid.statement full_name,
  3.     (migs.avg_total_user_cost * migs.avg_user_impact) *(migs.user_seeks + migs.user_scans) AS Impact,
  4.     migs.avg_user_impact *(migs.user_seeks + migs.user_scans) Avg_Estimated_Impact,
  5.     ‘CREATE NONCLUSTERED INDEX IDX_’ + sys.objects.name + ‘_N ON ‘
  6.         + sys.objects.name COLLATE DATABASE_DEFAULT
  7.         + ‘ ( ‘ + IsNull(mid.equality_columns, ”)
  8.         + CASE WHEN mid.inequality_columns IS NULL
  9.                 THEN ”
  10.             ELSE
  11.                 CASE WHEN mid.equality_columns IS NULL
  12.                     THEN ”
  13.                 ELSE ‘,’
  14.                 END + mid.inequality_columns
  15.             END + ‘ ) ‘
  16.         + CASE WHEN mid.included_columns IS NULL
  17.                 THEN ”
  18.             ELSE ‘INCLUDE (‘ + mid.included_columns + ‘)’ END
  19.         + ‘;’ AS CreateIndexStatement,
  20.     mid.equality_columns,
  21.     mid.inequality_columns,
  22.     mid.included_columns
  23. FROM sys.dm_db_missing_index_group_stats AS migs
  24.     INNER JOIN sys.dm_db_missing_index_groups AS mig ON migs.group_handle = mig.index_group_handle
  25.     INNER JOIN sys.dm_db_missing_index_details AS mid ON mig.index_handle = mid.index_handle
  26.         AND mid.database_id = DB_ID()
  27.     INNER JOIN sys.objects WITH (nolock) ON mid.OBJECT_ID = sys.objects.OBJECT_ID
  28. WHERE (migs.group_handle IN
  29.         (
  30.             SELECT TOP (500) group_handle
  31.             FROM sys.dm_db_missing_index_group_stats WITH (nolock)
  32.             ORDER BY (avg_total_user_cost * avg_user_impact) *(user_seeks + user_scans) DESC))
  33.     AND OBJECTPROPERTY(sys.objects.OBJECT_ID, ‘isusertable’)=1
  34. –ORDER BY [Impact] DESC, [full_name] DESC
  35. ORDER BY [table_name], [Impact] desc

查看应该被移除的索引

 

查看那些多余的、应该被移除的索引

SQL 1:

Code Snippet
  1. SELECT OBJECTNAME = OBJECT_NAME(I.OBJECT_ID),
  2.     INDEXNAME = I.NAME,
  3.     I.INDEX_ID
  4. FROM SYS.INDEXES I
  5.     JOIN SYS.OBJECTS O ON I.OBJECT_ID = O.OBJECT_ID
  6. WHERE OBJECTPROPERTY(O.OBJECT_ID,’IsUserTable’) = 1
  7.     AND I.INDEX_ID NOT IN(
  8.                             SELECT S.INDEX_ID
  9.                             FROM SYS.DM_DB_INDEX_USAGE_STATS S
  10.                             WHERE S.OBJECT_ID = I.OBJECT_ID
  11.                                 AND I.INDEX_ID = S.INDEX_ID
  12.                                 AND DATABASE_ID = DB_ID())
  13. ORDER BY OBJECTNAME, I.INDEX_ID, INDEXNAME ASC

SQL 2: 分析下面语句,移除那些没有必要的索引

Code Snippet
  1. SELECT DB_NAME(database_id)         AS  N’DataBaseName’  ,
  2.        OBJECT_NAME(U.object_id)     AS  N’Table_Name’    ,
  3.        I.name                       AS  N’Index_Name’    ,
  4.        user_seeks                   AS  N’用户索引查找次数’,
  5.        user_scans                   AS  N’用户索引扫描次数’,
  6.        last_user_seek               AS  N’最后查找时间’   ,
  7.        last_user_scan               AS  N’最后扫描时间’   ,
  8.        rows                         AS  N’表中的行数’
  9. FROM sys.dm_db_index_usage_stats AS U
  10.    INNER JOIN sys.indexes I ON U.index_id= I.index_idAND U.object_id= I.object_id
  11.   INNER JOIN sysindexesT ON I.object_id = T.id
  12. WHERE database_id= DB_ID(‘DbName’)
  13. AND OBJECT_NAME(U.object_id)=’TableName’
  14. ORDER BY user_seeks, user_scans, object_name(U.object_id);

基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理 - 伍华聪 - 博客园

mikel阅读(763)

来源: 基于SqlSugar的开发框架循序渐进介绍(2)– 基于中间表的查询处理 – 伍华聪 – 博客园

在前面介绍的SQLSugar的相关查询处理操作中,我们主要以单表的方式生成相关的实体类,并在查询的时候,对单表的字段进行条件的对比处理,从而返回对应的数据记录。本篇随笔介绍在一些外键或者中间表的处理中,如何遍历查询并获得所需的记录操作。

1、回顾单表的操作查询

我在《基于SqlSugar的开发框架的循序渐进介绍(1)–框架基础类的设计和使用》中介绍过的Customer表信息,就是一个单表的处理。

例如,我们对于一个简单的客户信息表,如下所示。

生成对应的实体对象CustomerInfo外,同时生成 CustomerPagedDto  的分页查询条件对象。

在继承基类后

复制代码
/// <summary>
/// 应用层服务接口实现
/// </summary>
public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService
{
       ....
}
复制代码

并重写 CreateFilteredQueryAsync 函数,从而实现了条件的精确查询处理。

复制代码
    /// <summary>
    /// 应用层服务接口实现
    /// </summary>
    public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService
    {
        /// <summary>
        /// 自定义条件处理
        /// </summary>
        /// <param name="input">查询条件Dto</param>
        /// <returns></returns>
        protected override ISugarQueryable<CustomerInfo> CreateFilteredQueryAsync(CustomerPagedDto input)
        {
            var query = base.CreateFilteredQueryAsync(input);

            query = query
                .WhereIF(!input.ExcludeId.IsNullOrWhiteSpace(), t => t.Id != input.ExcludeId) //不包含排除ID
                .WhereIF(!input.Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name)) //如需要精确匹配则用Equals
                                                                                             //年龄区间查询
                .WhereIF(input.AgeStart.HasValue, s => s.Age >= input.AgeStart.Value)
                .WhereIF(input.AgeEnd.HasValue, s => s.Age <= input.AgeEnd.Value)

                //创建日期区间查询
                .WhereIF(input.CreateTimeStart.HasValue, s => s.CreateTime >= input.CreateTimeStart.Value)
                .WhereIF(input.CreateTimeEnd.HasValue, s => s.CreateTime <= input.CreateTimeEnd.Value)
                ;

            return query;
        }
复制代码

在表的对应实体信息没有其他表关联的时候,我们直接通过SQLSugar的基础接口返回对象列表即可。

通过 CreateFilteredQueryAsync 的精确条件处理,我们就可以明确实体类的查询条件处理,因此对于CustomerPagedDto来说,就是可以有客户端传入,服务后端的基类进行处理了。

如基类的分页条件查询函数GetListAsync就是根据这个来处理的,它的实现代码如下所示。

复制代码
        /// <summary>
        /// 根据条件获取列表
        /// </summary>
        /// <param name="input">分页查询条件</param>
        /// <returns></returns>
        public virtual async Task<PagedResultDto<TEntity>> GetListAsync(TGetListInput input)
        {
            var query = CreateFilteredQueryAsync(input);
            var totalCount = await query.CountAsync();

            query = ApplySorting(query, input);
            query = ApplyPaging(query, input);

            var list = await query.ToListAsync();

            return new PagedResultDto<TEntity>(
               totalCount,
               list
           );
        }
复制代码

也就是说只要继承了 CustomerService ,我们默认调用基类的 GetListAsync 就可以返回对应的列表记录了。

如在Web API的控制器中调用获取记录返回,调用处理的代码如下所示。

复制代码
        /// <summary>
        /// 获取所有记录
        /// </summary>
        [HttpGet]
        [Route("all")]
        [HttpGet]public virtual async Task<ListResultDto<TEntity>> GetAllAsync()
        {
            //检查用户是否有权限,否则抛出MyDenyAccessException异常
            base.CheckAuthorized(AuthorizeKey.ListKey);

            return await _service.GetAllAsync();
        }
复制代码

而对于Winform的调用,我们这里首先利用代码生成工具生成对应的界面和代码

查看其调用的界面代码

而其中GetData中的函数部分内容如下所示。

复制代码
        /// <summary>
        /// 获取数据
        /// </summary>
        /// <returns></returns>
        private async Task<IPagedResult<CustomerInfo>> GetData()
        {
            CustomerPagedDto pagerDto = null;
            if (advanceCondition != null)
            {
                //如果有高级查询,那么根据输入信息构建查询条件
                pagerDto = new CustomerPagedDto(this.winGridViewPager1.PagerInfo);
                pagerDto = dlg.GetPagedResult(pagerDto);
            }
            else
            {
                //构建分页的条件和查询条件
                pagerDto = new CustomerPagedDto(this.winGridViewPager1.PagerInfo)
                {
                    //添加所需条件
                    Name = this.txtName.Text.Trim(),
                };

                //日期和数值范围定义
                //年龄,需在CustomerPagedDto中添加 int? 类型字段AgeStart和AgeEnd
                var Age = new ValueRange<int?>(this.txtAge1.Text, this.txtAge2.Text); //数值类型
                pagerDto.AgeStart = Age.Start;
                pagerDto.AgeEnd = Age.End;

                //创建时间,需在CustomerPagedDto中添加 DateTime? 类型字段CreationTimeStart和CreationTimeEnd
                var CreationTime = new TimeRange(this.txtCreationTime1.Text, this.txtCreationTime2.Text); //日期类型
                pagerDto.CreateTimeStart = CreationTime.Start;
                pagerDto.CreateTimeEnd = CreationTime.End;
            }

            var result = await BLLFactory<CustomerService>.Instance.GetListAsync(pagerDto);
            return result;
        }
复制代码

列表界面效果如下所示。

 

2、基于中间表的查询处理

前面的查询处理,主要就是针对没有任何关系的表实体对象的返回处理,但往往我们开发的时候,会涉及到很多相关的表,单独的表相对来说还是比较少,因此对表的关系遍历处理和中间表的关系转换,就需要在数据操作的时候考虑的了。

例如对于字典大类和字典项目的关系,如下所示。

以及在权限管理系统模块中,用户、角色、机构、权限等存在着很多中间表的关系,如下所示。

如对于字典表关系处理,我们采用Queryable<DictDataInfo, DictTypeInfo>的查询处理方式,可以联合两个表对象实体进行联合查询,如下代码所示。

复制代码
        /// <summary>
        /// 根据字典类型名称获取所有该类型的字典列表集合(Key为名称,Value为值)
        /// </summary>
        /// <param name="dictTypeName">字典类型名称</param>
        /// <returns></returns>
        public async Task<Dictionary<string, string>> GetDictByDictType(string dictTypeName)
        {
            var query = this.Client.Queryable<DictDataInfo, DictTypeInfo>(
                (d, t) => d.DictType_ID == t.Id && t.Name == dictTypeName)
                .Select(d => d); //联合条件获取对象

            query = query.OrderBy(d => d.DictType_ID).OrderBy(d => d.Seq);//排序
            var list = await query.ToListAsync();//获取列表

            var dict = new Dictionary<string, string>();
            foreach (var info in list)
            {
                if (!dict.ContainsKey(info.Name))
                {
                    dict.Add(info.Name, info.Value);
                }
            }
            return dict;
        }
复制代码

其中的Client对象是DbContext对象实例的Client属性,如下图所示。

这个对象是在DbContext对象中构建的,如下所示。

复制代码
            this.Client = new SqlSugarScope(new ConnectionConfig()
            {
                DbType = this.DbType,
                ConnectionString = this.ConnectionString,
                InitKeyType = InitKeyType.Attribute,
                IsAutoCloseConnection = true, //是否自动关闭连接
                AopEvents = new AopEvents
                {
                    OnLogExecuting = (sql, p) =>
                    {
                        //Log.Information(sql);
                        //Log.Information(string.Join(",", p?.Select(it => it.ParameterName + ":" + it.Value)));
                    }
                }
            });
复制代码

我们查看Queryable,可以看到这个SQLSugar基类函数 Queryable 提供了很多重载函数,也就是它们可以提供更多的表对象进行联合查询的,如下所示。

前面介绍的是外键的一对多的关系查询,通过两个对象之间进行的关系连接,从而实现另一个对象属性的对比查询操作的。

对于中间表的处理,也是类似的情况,我们通过对比中间表的属性,从而实现条件的过滤处理。如下是对于角色中相关关系的中间表查询。

复制代码
        /// <summary>
        /// 根据用户ID获取对应的角色列表
        /// </summary>
        /// <param name="userID">用户ID</param>
        /// <returns></returns>
        private async Task<List<RoleInfo>> GetByUser(int userID)
        {
            var query = this.Client.Queryable<RoleInfo, User_RoleInfo>(
            (t, m) => t.Id == m.Role_ID && m.User_ID == userID)
            .Select(t => t); //联合条件获取对象

            query = query.OrderBy(t => t.CreateTime);//排序
            var list = await query.ToListAsync();//获取列表
            return list;
        }

        /// <summary>
        /// 根据机构获取对应的角色列表(判断机构角色中间表)
        /// </summary>
        /// <param name="ouID">机构的ID</param>
        /// <returns></returns>
        public async Task<List<RoleInfo>> GetRolesByOu(int ouID)
        {
            var query = this.Client.Queryable<RoleInfo, OU_RoleInfo>(
            (t, m) => t.Id == m.Role_ID && m.Ou_ID == ouID)
            .Select(t => t); //联合条件获取对象

            query = query.OrderBy(t => t.CreateTime);//排序
            var list = await query.ToListAsync();//获取列表
            return list;
        }
复制代码

通过联合查询中间表对象信息,可以对它的字段属性进行条件联合,从而获得所需的记录。

这里User_RoleInfo和Ou_RoleInfo表也是根据中间表的属性生成的,不过它们在业务层并没有任何关联操作,也不需要生成对应的Service层,因此只需要生成相关的Model类实体即可。

复制代码
    /// <summary>
    /// 用户角色关联
    /// </summary>
    [SugarTable("T_ACL_User_Role")]
    public class User_RoleInfo
    {         
        /// <summary>
        /// 用户ID
        /// </summary>
        [Required]
        public virtual int User_ID { get; set; }

        /// <summary>
        /// 角色ID
        /// </summary>
        [Required]
        public virtual int Role_ID { get; set; }

    }
复制代码
复制代码
    /// <summary>
    /// 机构角色关联
    /// </summary>
    [SugarTable("T_ACL_OU_Role")]
    public class OU_RoleInfo 
    {         
        /// <summary>
        /// 机构ID
        /// </summary>
        [Required]
        public virtual int Ou_ID { get; set; }

        /// <summary>
        /// 角色ID
        /// </summary>
        [Required]
        public virtual int Role_ID { get; set; }
    }
复制代码

可以看到这两个实体不同于其他实体,它们没有基类继承关系,而一般标准的实体是有的。

复制代码
    /// <summary>
    /// 角色信息
    /// </summary>
    [SugarTable("T_ACL_Role")]
    public class RoleInfo : Entity<int> {  }


    /// <summary>
    /// 功能菜单
    /// </summary>
    [SugarTable("T_ACL_Menu")]
    public class MenuInfo : Entity<string> { }
复制代码

所以我们就不需要构建它们的Service层来处理数据,它的存在合理性只是在于能够和其他实体对象进行表的联合查询处理而且。

最后贴上一个整合SqlSugar处理而完成的系统基础框架的Winform端界面,其中包括用户、组织机构、角色管理、权限管理、菜单管理、日志、字典、客户信息等业务表的处理。

以证所言非虚。

 

系列文章:

基于SqlSugar的开发框架的循序渐进介绍(1)–框架基础类的设计和使用

基于SqlSugar的开发框架循序渐进介绍(2)– 基于中间表的查询处理

基于SqlSugar的开发框架循序渐进介绍(3)– 实现代码生成工具Database2Sharp的整合开发

 

基于SqlSugar的开发框架循序渐进介绍(1)--框架基础类的设计和使用 - 伍华聪 - 博客园

mikel阅读(791)

来源: 基于SqlSugar的开发框架循序渐进介绍(1)–框架基础类的设计和使用 – 伍华聪 – 博客园

在实际项目开发中,我们可能会碰到各种各样的项目环境,有些项目需要一个大而全的整体框架来支撑开发,有些中小项目这需要一些简单便捷的系统框架灵活开发。目前大型一点的框架,可以采用ABP或者ABP VNext的框架,两者整体思路和基础设计类似,不过ABP侧重于一个独立完整的项目框架,开发的时候统一整合处理;而ABP VNext则是以微服务架构为基础,各个模块独立开发,既可以整合在一个项目中,也可以以微服务进行单独发布,并统一通过网关处理进行交流。不管ABP或者ABP VNext框架,都集合了.NET CORE领域众多技术为一体,并且基础类设计上,错综复杂,关系较多,因此开发学习有一定的门槛,中小型项目应用起来有一定的费劲之处。本系列随笔介绍底层利用SQLSugar来做ORM数据访问模块,设计一个简单便捷一点的框架,本篇从基础开始介绍一些框架内容,参照一些ABP/ABP VNext中的一些类库处理,来承载类似条件分页信息,查询条件处理等处理细节。

1、基于SQLSugar开发框架的架构设计

主要的设计模块场景如下所示。

 

为了避免像ABP VNext框架那样分散几十个项目,我们尽可能聚合内容放在一个项目里面。

1)其中一些常用的类库,以及SQLSugar框架的基类放在框架公用模块里面。

2)Winform开发相关的基础界面以及通用组件内容,放在基础Winform界面库BaseUIDx项目中。

3)基础核心数据模块SugarProjectCore,主要就是开发业务所需的数据处理和业务逻辑的项目,为了方便,我们区分Interface、Modal、Service三个目录来放置不同的内容,其中Modal是SqlSugar的映射实体,Interface是定义访问接口,Service是提供具体的数据操作实现。其中Service里面一些框架基类和接口定义,统一也放在公用类库里面。

4)Winform应用模块,主要就是针对业务开发的WInform界面应用,而WInform开发为了方便,也会将一些基础组件和基类放在了BaseUIDx的Winform专用的界面库里面。

5)WebAPI项目采用基于.net Core6的项目开发,通过调用SugarProjectCore实现相关控制器API的发布,并整合Swagger发布接口,供其他前端界面应用进行调用。

6)纯前端通过API进行调用Web API的接口,纯前端模块可以包含Vue3&Element项目,以及基于EelectronJS应用,发布跨平台的基于浏览器的应用界面,以及其他App或者小程序整合Web API进行业务数据的处理或者展示需要。

如后端开发,我们可以在VS2022中进行管理,管理开发Winform项目、Web API项目等。

Winform界面,我们可以采用基于.net Framework开发或者.net core6进行开发均可,因为我们的SugarProjectCore项目是采用.net Standard模式开发,兼容两者。这里以权限模块来进行演示整合使用。

 

而纯前端的项目,我们可以基于VSCode或者 HBuilderX等工具进行项目的管理开发工作。

 

2、框架基础类的定义和处理

在开发一个易于使用的框架的时候,主要目的就是减少代码开发,并尽可能通过基类和泛型约束的方式,提高接口的通用性,并通过结合代码生成工具的方式,来提高标准项目的开发效率。

那么我们这里基于SqlSugar的ORM处理,来实现常规数据的增删改查等常规操作的时候,我们是如何进行这些接口的封装处理的呢。

例如,我们对于一个简单的客户信息表,如下所示。

那么它生成的SqlSugar实体类如下所示。

复制代码
    /// <summary>
    /// 客户信息
    /// 继承自Entity,拥有Id主键属性
    /// </summary>
    [SugarTable("T_Customer")]
    public class CustomerInfo : Entity<string>
    {
        /// <summary>
        /// 默认构造函数(需要初始化属性的在此处理)
        /// </summary>
        public CustomerInfo()
        {
            this.CreateTime = System.DateTime.Now;
        }

        #region Property Members

        /// <summary>
        /// 姓名
        /// </summary>
        public virtual string Name { get; set; }

        /// <summary>
        /// 年龄
        /// </summary>
        public virtual int Age { get; set; }

        /// <summary>
        /// 创建人
        /// </summary>
        public virtual string Creator { get; set; }

        /// <summary>
        /// 创建时间
        /// </summary>
        public virtual DateTime CreateTime { get; set; }

        #endregion
    }
复制代码

其中 Entity<string> 是我们根据需要定义一个基类实体对象,主要就是定义一个Id的属性来处理,毕竟对于一般表对象的处理,SqlSugar需要Id的主键定义(非中间表处理)。

复制代码
    [Serializable]
    public abstract class Entity<TPrimaryKey> : IEntity<TPrimaryKey>
    {
        /// <summary>
        /// 实体类唯一主键
        /// </summary>
        [SqlSugar.SugarColumn(IsPrimaryKey = true, ColumnDescription = "主键")]
        public virtual TPrimaryKey Id { get; set; }
    }
复制代码

而IEntity<T>定义了一个接口

复制代码
    public interface IEntity<TPrimaryKey>
    {
        /// <summary>
        /// 实体类唯一主键
        /// </summary>
        TPrimaryKey Id { get; set; }
    }
复制代码

以上就是实体类的处理,我们一般为了查询信息,往往通过一些条件传入进行处理,那么我们就需要定义一个通用的分页查询对象,供我们精准进行条件的处理。

生成一个以***PageDto的对象类,如下所示。

复制代码
    /// <summary>
    /// 用于根据条件分页查询,DTO对象
    /// </summary>
    public class CustomerPagedDto : PagedAndSortedInputDto, IPagedAndSortedResultRequest
    {
        /// <summary>
        /// 默认构造函数
        /// </summary>
        public CustomerPagedDto() : base() { }

        /// <summary>
        /// 参数化构造函数
        /// </summary>
        /// <param name="skipCount">跳过的数量</param>
        /// <param name="resultCount">最大结果集数量</param>
        public CustomerPagedDto(int skipCount, int resultCount) : base(skipCount, resultCount)
        {
        }

        /// <summary>
        /// 使用分页信息进行初始化SkipCount 和 MaxResultCount
        /// </summary>
        /// <param name="pagerInfo">分页信息</param>
        public CustomerPagedDto(PagerInfo pagerInfo) : base(pagerInfo)
        {
        }

        #region Property Members

        /// <summary>
        /// 不包含的对象的ID,用于在查询的时候排除对应记录
        /// </summary>
        public virtual string ExcludeId { get; set; }

        /// <summary>
        /// 姓名
        /// </summary>
        public virtual string Name { get; set; }

        /// <summary>
        /// 年龄-开始
        /// </summary>
        public virtual int? AgeStart { get; set; }
        /// <summary>
        /// 年龄-结束
        /// </summary>
        public virtual int? AgeEnd { get; set; }

        /// <summary>
        /// 创建时间-开始
        /// </summary>
        public DateTime? CreateTimeStart { get; set; }
        /// <summary>
        /// 创建时间-结束
        /// </summary>
        public DateTime? CreateTimeEnd { get; set; }

        #endregion
    }
复制代码

其中PagedAndSortedInputDto, IPagedAndSortedResultRequest都是参考来自于ABP/ABP VNext的处理方式,这样我们可以便于数据访问基类的查询处理操作。

接着我们定义一个基类MyCrudService,并传递如相关的泛型约束,如下所示

复制代码
    /// <summary>
    /// 基于SqlSugar的数据库访问操作的基类对象
    /// </summary>
    /// <typeparam name="TEntity">定义映射的实体类</typeparam>
    /// <typeparam name="TKey">主键的类型,如int,string等</typeparam>
    /// <typeparam name="TGetListInput">或者分页信息的条件对象</typeparam>
    public abstract class MyCrudService<TEntity, TKey, TGetListInput> : 
        IMyCrudService<TEntity, TKey, TGetListInput>
        where TEntity : class, IEntity<TKey>, new()
        where TGetListInput : IPagedAndSortedResultRequest
复制代码

我们先忽略基类接口的相关实现细节,我们看看对于这个MyCrudService和 IMyCrudService 我们应该如何使用的。

首先我们定义一个应用层的接口ICustomerService如下所示。

复制代码
    /// <summary>
    /// 客户信息服务接口
    /// </summary>
    public interface ICustomerService : IMyCrudService<CustomerInfo, string, CustomerPagedDto>, ITransientDependency
    {

    }
复制代码

然后实现在CustomerService中实现它的接口。

    /// <summary>
    /// 应用层服务接口实现
    /// </summary>
    public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService

这样我们对于特定Customer的接口在ICustomer中定义,标准接口直接调用基类即可。

基类MyCrudService提供重要的两个接口,让子类进行重写,以便于进行准确的条件处理和排序处理,如下代码所示。

复制代码
    /// <summary>
    /// 基于SqlSugar的数据库访问操作的基类对象
    /// </summary>
    /// <typeparam name="TEntity">定义映射的实体类</typeparam>
    /// <typeparam name="TKey">主键的类型,如int,string等</typeparam>
    /// <typeparam name="TGetListInput">或者分页信息的条件对象</typeparam>
    public abstract class MyCrudService<TEntity, TKey, TGetListInput> : 
        IMyCrudService<TEntity, TKey, TGetListInput>
        where TEntity : class, IEntity<TKey>, new()
        where TGetListInput : IPagedAndSortedResultRequest
    {
        /// <summary>
        /// 留给子类实现过滤条件的处理
        /// </summary>
        /// <returns></returns>
        protected virtual ISugarQueryable<TEntity> CreateFilteredQueryAsync(TGetListInput input)
        {
            return EntityDb.AsQueryable();
        }
        /// <summary>
        /// 默认排序,通过ID进行排序
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        protected virtual ISugarQueryable<TEntity> ApplyDefaultSorting(ISugarQueryable<TEntity> query)
        {
            if (typeof(TEntity).IsAssignableTo<IEntity<TKey>>())
            {
                return query.OrderBy(e => e.Id);
            }
            else
            {
                return query.OrderBy("Id");
            }
        }        
    }
复制代码

对于Customer特定的业务对象来说,我们需要实现具体的条件查询细节和排序条件,毕竟我们父类没有约束确定实体类有哪些属性的情况下,这些就交给子类做最合适了。

复制代码
    /// <summary>
    /// 应用层服务接口实现
    /// </summary>
    public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService
    {
        /// <summary>
        /// 自定义条件处理
        /// </summary>
        /// <param name="input">查询条件Dto</param>
        /// <returns></returns>
        protected override ISugarQueryable<CustomerInfo> CreateFilteredQueryAsync(CustomerPagedDto input)
        {
            var query = base.CreateFilteredQueryAsync(input);

            query = query
                .WhereIF(!input.ExcludeId.IsNullOrWhiteSpace(), t => t.Id != input.ExcludeId) //不包含排除ID
                .WhereIF(!input.Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name)) //如需要精确匹配则用Equals
                                                                                             //年龄区间查询
                .WhereIF(input.AgeStart.HasValue, s => s.Age >= input.AgeStart.Value)
                .WhereIF(input.AgeEnd.HasValue, s => s.Age <= input.AgeEnd.Value)

                //创建日期区间查询
                .WhereIF(input.CreateTimeStart.HasValue, s => s.CreateTime >= input.CreateTimeStart.Value)
                .WhereIF(input.CreateTimeEnd.HasValue, s => s.CreateTime <= input.CreateTimeEnd.Value)
                ;

            return query;
        }

        /// <summary>
        /// 自定义排序处理
        /// </summary>
        /// <param name="query">可查询LINQ</param>
        /// <returns></returns>
        protected override ISugarQueryable<CustomerInfo> ApplyDefaultSorting(ISugarQueryable<CustomerInfo> query)
        {
            return query.OrderBy(t => t.CreateTime, OrderByType.Desc);

            //先按第一个字段排序,然后再按第二字段排序
            //return base.ApplySorting(query, input).OrderBy(s=>s.Customer_ID).OrderBy(s => s.Seq);
        }
    }
复制代码

通过 CreateFilteredQueryAsync 的精确条件处理,我们就可以明确实体类的查询条件处理,因此对于CustomerPagedDto来说,就是可以有客户端传入,服务后端的基类进行处理了。

如基类的分页条件查询函数GetListAsync就是根据这个来处理的,它的实现代码如下所示。

复制代码
        /// <summary>
        /// 根据条件获取列表
        /// </summary>
        /// <param name="input">分页查询条件</param>
        /// <returns></returns>
        public virtual async Task<PagedResultDto<TEntity>> GetListAsync(TGetListInput input)
        {
            var query = CreateFilteredQueryAsync(input);
            var totalCount = await query.CountAsync();

            query = ApplySorting(query, input);
            query = ApplyPaging(query, input);

            var list = await query.ToListAsync();

            return new PagedResultDto<TEntity>(
               totalCount,
               list
           );
        }
复制代码

而其中 ApplySorting 就是根据条件决定是否选择子类实现的默认排序进行处理的。

复制代码
        /// <summary>
        /// 记录排序处理
        /// </summary>
        /// <returns></returns>
        protected virtual ISugarQueryable<TEntity> ApplySorting(ISugarQueryable<TEntity> query, TGetListInput input)
        {
            //Try to sort query if available
            if (input is ISortedResultRequest sortInput)
            {
                if (!sortInput.Sorting.IsNullOrWhiteSpace())
                {
                    return query.OrderBy(sortInput.Sorting);
                }
            }

            //IQueryable.Task requires sorting, so we should sort if Take will be used.
            if (input is ILimitedResultRequest)
            {
                return ApplyDefaultSorting(query);
            }

            //No sorting
            return query;
        }
复制代码

对于获取单一对象,我们一般提供一个ID主键获取即可。

复制代码
        /// <summary>
        /// 根据ID获取单一对象
        /// </summary>
        /// <param name="id">主键ID</param>
        /// <returns></returns>
        public virtual async Task<TEntity> GetAsync(TKey id)
        {
            return await EntityDb.GetByIdAsync(id);
        }
复制代码

也可以根据用户的Express条件进行处理,在基类我们定义很多这样的Express条件处理,便于子类进行条件处理的调用。如对于删除,可以指定ID,也可以指定条件删除。

复制代码
        /// <summary>
        /// 删除指定ID的对象
        /// </summary>
        /// <param name="id">记录ID</param>
        /// <returns></returns>
        public virtual async Task<bool> DeleteAsync(TKey id)
        {
            return await EntityDb.DeleteByIdAsync(id);
        }
复制代码
复制代码
/// <summary>
        /// 根据指定条件,删除集合
        /// </summary>
        /// <param name="input">表达式条件</param>
        /// <returns></returns>
        public virtual async Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> input)
        {
            var result = await EntityDb.DeleteAsync(input);
            return result;
        }
复制代码

如判断是否存在也是一样处理

复制代码
        /// <summary>
        /// 判断是否存在指定条件的记录
        /// </summary>
        /// <param name="id">ID 主键</param>
        /// <returns></returns>
        public virtual async Task<bool> IsExistAsync(TKey id)
        {
            var info = await EntityDb.GetByIdAsync(id);
            var result = (info != null);
            return result;
        }

        /// <summary>
        /// 判断是否存在指定条件的记录
        /// </summary>
        /// <param name="input">表达式条件</param>
        /// <returns></returns>
        public virtual async Task<bool> IsExistAsync(Expression<Func<TEntity, bool>> input)
        {
            var result = await EntityDb.IsAnyAsync(input);
            return result;
        }
复制代码

关于Web API的处理,我在随笔《基于SqlSugar的数据库访问处理的封装,在.net6框架的Web API上开发应用》中也有介绍,主要就是先弄好.net6的开发环境,然后在进行相关的项目开发即可。

根据项目的需要,我们定义了一些控制器的基类,用于实现不同的功能。

 

其中ControllerBase是.net core Web API中的标准控制器基类,我们由此派生一个LoginController用于登录授权,而BaseApiController则处理常规接口用户身份信息,而BusinessController则是对标准的增删改查等基础接口进行的封装,我们实际开发的时候,只需要开发编写类似CustomerController基类即可。

BaseApiController没有什么好介绍的,就是封装一下获取用户的身份信息。

可以通过下面代码获取接口用户的Id

        /// <summary>
        /// 当前用户身份ID
        /// </summary>
        protected virtual string? CurrentUserId => HttpContext.User.FindFirst(JwtClaimTypes.Id)?.Value;

而BusinessController控制器则是继承这个BaseApiController即可。通过泛型约束传入相关的对象信息。

复制代码
    /// <summary>
    /// 本控制器基类专门为访问数据业务对象而设的基类
    /// </summary>
    /// <typeparam name="TEntity">定义映射的实体类</typeparam>
    /// <typeparam name="TKey">主键的类型,如int,string等</typeparam>
    /// <typeparam name="TGetListInput">或者分页信息的条件对象</typeparam>
    [Route("[controller]")]
    [Authorize] //需要授权登录访问
    public class BusinessController<TEntity, TKey, TGetListInput> : BaseApiController
        where TEntity : class, IEntity<TKey>, new()
        where TGetListInput : IPagedAndSortedResultRequest
    {
        /// <summary>
        /// 通用基础操作接口
        /// </summary>
        protected IMyCrudService<TEntity, TKey, TGetListInput> _service { get; set; }

        /// <summary>
        /// 构造函数,初始化基础接口
        /// </summary>
        /// <param name="service">通用基础操作接口</param>
        public BusinessController(IMyCrudService<TEntity, TKey, TGetListInput> service)
        {
            this._service = service;
        }

....
复制代码

这个基类接收一个符合基类接口定义的对象作为基类增删删改查等处理方法的接口对象。在具体的CustomerController中的定义处理如下所示。

复制代码
    /// <summary>
    /// 客户信息的控制器对象
    /// </summary>
    public class CustomerController : BusinessController<CustomerInfo, string, CustomerPagedDto>
    {
        private ICustomerService _customerService;

        /// <summary>
        /// 构造函数,并注入基础接口对象
        /// </summary>
        /// <param name="customerService"></param>
        public CustomerController(ICustomerService customerService) :base(customerService)
        {
            this._customerService = customerService;
        }
    }
复制代码

这样就可以实现基础的相关操作了。如果需要特殊的接口实现,那么定义方法实现即可。

类似字典项目中的控制器处理代码如下所示。定义好HTTP方法,路由信息等即可。

复制代码
        /// <summary>
        /// 根据字典类型ID获取所有该类型的字典列表集合(Key为名称,Value为值)
        /// </summary>
        /// <param name="dictTypeId">字典类型ID</param>
        /// <returns></returns>
        [HttpGet]
        [Route("by-typeid/{dictTypeId}")]
        public async Task<Dictionary<string, string>> GetDictByTypeID(string dictTypeId)
        {
            return await _dictDataService.GetDictByTypeID(dictTypeId);
        }

        /// <summary>
        /// 根据字典类型名称获取所有该类型的字典列表集合(Key为名称,Value为值)
        /// </summary>
        /// <param name="dictTypeName">字典类型名称</param>
        /// <returns></returns>
        [HttpGet]
        [Route("by-typename/{dictTypeName}")]
        public async Task<Dictionary<string, string>> GetDictByDictType(string dictTypeName)
        {
            return await _dictDataService.GetDictByDictType(dictTypeName);
        }
复制代码

 

系列文章:

基于SqlSugar的开发框架的循序渐进介绍(1)–框架基础类的设计和使用

基于SqlSugar的开发框架循序渐进介绍(2)– 基于中间表的查询处理

基于SqlSugar的开发框架循序渐进介绍(3)– 实现代码生成工具Database2Sharp的整合开发

 

基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发 - 伍华聪 - 博客园

mikel阅读(666)

来源: 基于SqlSugar的开发框架循序渐进介绍(3)– 实现代码生成工具Database2Sharp的整合开发 – 伍华聪 – 博客园

我喜欢在一个项目开发模式成熟的时候,使用代码生成工具Database2Sharp来配套相关的代码生成,对于我介绍的基于SQLSugar的开发框架,从整体架构确定下来后,我就着手为它们量身定做相关的代码开发,这样可以在后续整合项目功能的时候,利用代码生成工具快速的生成所需要模块的骨架代码,然后在这个基础上逐渐增加自定义的内容即可,方便快捷。本篇随笔介绍基于SQLSugar的开发框架,对框架中涉及到的各个分层或者模块代码进行生成的处理。

1、回顾项目的架构和模块内容

在前面几篇随笔中,大概介绍过了基于SQLSugar的开发框架主要的设计模块,场景如下所示。

基础核心数据模块SugarProjectCore,主要就是开发业务所需的数据处理和业务逻辑的项目,为了方便,我们区分Interface、Modal、Service三个目录来放置不同的内容,其中Modal是SqlSugar的映射实体,Interface是定义访问接口,Service是提供具体的数据操作实现。其中Service里面一些框架基类和接口定义,统一也放在公用类库里面。

Winform界面,我们可以采用基于.net Framework开发或者.net core6进行开发均可,因为我们的SugarProjectCore项目是采用.net Standard模式开发,兼容两者。

这里以权限模块来进行演示整合使用,我在构建代码生成工具代码模板的时候,反复利用项目中测试没问题的项目代码指导具体的模板编写,这样编写出来的模板就会完美符合实际的项目需要了。

在项目代码及模板完成后,利用代码生成工具快速生成代码,相互促进情况下,也完成了Winform项目的界面代码生成,生成包括普通的列表界面,以及主从表Winform界面代码生成。

最后权限系统的Winform项目如下所示。

在前面随笔《基于SqlSugar的开发框架循序渐进介绍(2)– 基于中间表的查询处理》中介绍了基础功能的一些处理,其中也介绍到了Winform界面端的界面效果,这个以SqlSugar底层处理,最终把权限、字典等模块整合到一起,完成一个项目开发所需要的框架结构内容。整个系统包括用户管理、组织机构管理、角色管理、功能权限管理、菜单管理、字段权限管理、黑白名单、操作日志、字典管理、客户信息等模块内容。

在代码生成工具中,我们整合了基于SqlSugar的开发框架的项目代码生成,包括框架基础的代码生成,以及Winform界面代码生成两个部分,框架项目及Winform界面效果如上图所示。

 

2、整合代码生成工具Database2Sharp进行SqlSugar框架代码生成

前面随笔介绍过基于SqlSugar核心Core项目的组成。

基础核心数据模块SugarProjectCore,主要就是开发业务所需的数据处理和业务逻辑的项目,为了方便,我们区分Interface、Modal、Service三个目录来放置不同的内容,其中Modal是SqlSugar的映射实体,Interface是定义访问接口,Service是提供具体的数据操作实现。

对于Modal层的类代码生成,常规的普通表(非中间表),我们根据项目所需要,生成如下代码。目的是利用它定义好对应的主键Id,并通过接口约束实体类。

复制代码
    /// <summary>
    /// 客户信息
    /// 继承自Entity,拥有Id主键属性
    /// </summary>
    [SugarTable("T_Customer")]
    public class CustomerInfo : Entity<string>
复制代码

而对于中间表,我们不要它的继承继承关系。

复制代码
    /// <summary>
    /// 用户角色关联
    /// </summary>
    [SugarTable("T_ACL_User_Role")]
    public class User_RoleInfo
    {
    }
复制代码

只需要简单的标注好SugarTable属性,让他可以和其他业务表进行关联查询即可。

复制代码
        /// <summary>
        /// 根据用户ID获取对应的角色列表
        /// </summary>
        /// <param name="userID">用户ID</param>
        /// <returns></returns>
        private async Task<List<RoleInfo>> GetByUser(int userID)
        {
            var query = this.Client.Queryable<RoleInfo, User_RoleInfo>(
            (t, m) => t.Id == m.Role_ID && m.User_ID == userID)
            .Select(t => t); //联合条件获取对象

            query = query.OrderBy(t => t.CreateTime);//排序
            var list = await query.ToListAsync();//获取列表
            return list;
        }
复制代码

对于接口层的类,我们只需要按固定的继承关系处理好,以及类的名称变化即可。

复制代码
    /// <summary>
    /// 系统用户信息,应用层服务接口定义
    /// </summary>
    public interface IUserService : IMyCrudService<UserInfo, int, UserPagedDto>, ITransientDependency
    {
    }
复制代码

其中 IMyCrudService 是我们定义的基类接口,保存常规的增删改查等的处理基类,通过传入泛型进行约束接口参数类型和返回值。

基类接口尽可能满足实际项目接口所需,这样可以减少子类的代码编写,以及获得统一调用基类函数的便利。

 

对于中间表,我们除了生成实体类外,不需要生成其他接口和接口实现层,因为我们不单独调用它们。

对于具体业务对象对应的接口实现,我们除了确定它的继承关系外,我们还会重写它们的一些基类函数,从而实现更加精准的处理。

接口实现类的定义如下所示。

复制代码
    /// <summary>
    /// 应用层服务接口实现
    /// </summary>
    public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService
    {

    }
复制代码

一般情况下,我们至少需要在子类重写 CreateFilteredQueryAsync 和 ApplyDefaultSorting 两个函数,前者是条件的查询处理,后者是默认的排序处理操作。

复制代码
        /// <summary>
        /// 自定义条件处理
        /// </summary>
        /// <param name="input">查询条件Dto</param>
        /// <returns></returns>
        protected override ISugarQueryable<CustomerInfo> CreateFilteredQueryAsync(CustomerPagedDto input)
        {
            var query = base.CreateFilteredQueryAsync(input);

            query = query
                .WhereIF(!input.ExcludeId.IsNullOrWhiteSpace(), t => t.Id != input.ExcludeId) //不包含排除ID
                .WhereIF(!input.Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name)) //如需要精确匹配则用Equals
                                                                                             //年龄区间查询
                .WhereIF(input.AgeStart.HasValue, s => s.Age >= input.AgeStart.Value)
                .WhereIF(input.AgeEnd.HasValue, s => s.Age <= input.AgeEnd.Value)

                //创建日期区间查询
                .WhereIF(input.CreateTimeStart.HasValue, s => s.CreateTime >= input.CreateTimeStart.Value)
                .WhereIF(input.CreateTimeEnd.HasValue, s => s.CreateTime <= input.CreateTimeEnd.Value)
                ;

            return query;
        }

        /// <summary>
        /// 自定义排序处理
        /// </summary>
        /// <param name="query">可查询LINQ</param>
        /// <returns></returns>
        protected override ISugarQueryable<CustomerInfo> ApplyDefaultSorting(ISugarQueryable<CustomerInfo> query)
        {
            return query.OrderBy(t => t.CreateTime, OrderByType.Desc);

            //如果先按第一个字段排序,然后再按第二字段排序,示例代码
            //return base.ApplySorting(query, input).OrderBy(s=>s.Customer_ID).OrderBy(s => s.Seq);
        }
复制代码

根据这些规则,编写我们所需的模板代码,让我们选择的数据库表名称、注释,以及表字段的名称、类型、注释,外键主键关系等信息为我们模板所用。

如下所示代码是NVelocity模板代码,用于生成上面的条件查询处理的,可以稍作了解。

复制代码
        /// <summary>
        /// 自定义条件处理
        /// </summary>
        /// <param name="input">查询条件Dto</param>
        /// <returns></returns>
        protected override ISugarQueryable<${ClassName}Info> CreateFilteredQueryAsync(${ClassName}PagedDto input)
        {
            var query = base.CreateFilteredQueryAsync(input);
             query = query
#if(${PrimaryKeyNetType}=="string")
                .WhereIF(!input.ExcludeId.IsNullOrWhiteSpace(), t=>t.Id != input.ExcludeId) //不包含排除ID
#else
                .WhereIF(input.ExcludeId.HasValue, t=>t.Id != input.ExcludeId) //不包含排除ID
#end
#foreach($EntityProperty in $EntityPropertyList) 
#if(${EntityProperty.ColumnInfo.IsForeignKey} || ${EntityProperty.PropertyName}  == "Status" || ${EntityProperty.PropertyName}  == "State" || ${EntityProperty.PropertyName}  == "PID" || ${EntityProperty.PropertyName}  == "Deleted")
                .WhereIF(#if(${EntityProperty.ColumnInfo.IsNumeric})input.${EntityProperty.PropertyName}.HasValue#else!input.${EntityProperty.PropertyName}.IsNullOrWhiteSpace()#end, s => s.${EntityProperty.PropertyName} == input.${EntityProperty.PropertyName})
#elseif(${EntityProperty.ColumnInfo.IsDateTime} || ${EntityProperty.ColumnInfo.IsNumeric})
                //${EntityProperty.Description}区间查询
                .WhereIF(input.${EntityProperty.PropertyName}Start.HasValue, s => s.${EntityProperty.PropertyName} >= input.${EntityProperty.PropertyName}Start.Value)
                .WhereIF(input.${EntityProperty.PropertyName}End.HasValue, s => s.${EntityProperty.PropertyName} <= input.${EntityProperty.PropertyName}End.Value)
#elseif(${EntityProperty.ColumnInfo.NetType.Alias.ToLower()} != "byte[]" && ${EntityProperty.ColumnInfo.Name.Name.ToString()} != "AttachGUID")
                .WhereIF(#if(${EntityProperty.NetType.EndsWith("?")})input.${EntityProperty.PropertyName}.HasValue, t => t.${EntityProperty.PropertyName} == input.${EntityProperty.PropertyName}#else!input.${EntityProperty.PropertyName}.IsNullOrWhiteSpace(), t => t.${EntityProperty.PropertyName}.Contains(input.${EntityProperty.PropertyName})#end) //如需要精确匹配则用Equals
#end ##endif
#end

#if(${HasCreationTime}) 
                //创建日期区间查询
                .WhereIF(input.CreationTimeStart.HasValue, s => s.CreationTime >= input.CreationTimeStart.Value)
                .WhereIF(input.CreationTimeEnd.HasValue, s => s.CreationTime <= input.CreationTimeEnd.Value)
#else
                //创建日期区间查询(参考)
                //.WhereIF(input.CreationTimeStart.HasValue, s => s.CreationTime >= input.CreationTimeStart.Value)
                //.WhereIF(input.CreationTimeEnd.HasValue, s => s.CreationTime <= input.CreationTimeEnd.Value)
#end;
            return query;
        }
复制代码

 

当我们完成所需的模板代码开发后,就在代码生成工具主体界面中整合相关的生成功能菜单,界面效果如下所示。

通过菜单选择【SqlSugar框架代码生成】,进一步选择数据库中的表进行生成,一步步处理即可,最后列出所选数据库表,并确认生成操作,即可生成SqlSugar框架核心项目的代码,如下图所示。

选择表进行生成后,生成的实体模型类如下所示,包括生成了中间表的实体类。

 

而接口实现则是根据具体的业务对象规则进行生成。

 

 

3、SqlSugar项目中Winform界面的生成

Winform界面包括普通列表/编辑界面处理,以及主从表界面处理两个部分,如下图所示。

生成的简单业务表界面,包括分页列表展示界面,在列表界面中整合查看、编辑、新增、删除、导入、导出、查询/高级查询等功能,整合的编辑界面也是依据数据库表的信息进行生成的。

列表界面和编辑界面效果如下所示。

而主从表界面生成的效果如下所示。

我们看看生成的Winform列表界面代码,如下所示。

另外我们把一些常用的处理逻辑放在函数中统一处理,如AddData、EditData、DeleteData、BindData、GetData、ImportData、ExportData等等,如下所示。

 

在获取数据的时候,我们根据用户的条件,构建一个分页查询对象传递,调用接口获得数据后,进行分页控件的绑定处理即可。

复制代码
        /// <summary>
        /// 获取数据
        /// </summary>
        /// <returns></returns>
        private async Task<IPagedResult<CustomerInfo>> GetData()
        {
            CustomerPagedDto pagerDto = null;
            if (advanceCondition != null)
            {
                //如果有高级查询,那么根据输入信息构建查询条件
                pagerDto = new CustomerPagedDto(this.winGridViewPager1.PagerInfo);
                pagerDto = dlg.GetPagedResult(pagerDto);
            }
            else
            {
                //构建分页的条件和查询条件
                pagerDto = new CustomerPagedDto(this.winGridViewPager1.PagerInfo)
                {
                    //添加所需条件
                    Name = this.txtName.Text.Trim(),
                };

                //日期和数值范围定义
                //年龄,需在CustomerPagedDto中添加 int? 类型字段AgeStart和AgeEnd
                var Age = new ValueRange<int?>(this.txtAge1.Text, this.txtAge2.Text); //数值类型
                pagerDto.AgeStart = Age.Start;
                pagerDto.AgeEnd = Age.End;

                //创建时间,需在CustomerPagedDto中添加 DateTime? 类型字段CreationTimeStart和CreationTimeEnd
                var CreationTime = new TimeRange(this.txtCreationTime1.Text, this.txtCreationTime2.Text); //日期类型
                pagerDto.CreateTimeStart = CreationTime.Start;
                pagerDto.CreateTimeEnd = CreationTime.End;
            }

            var result = await BLLFactory<CustomerService>.Instance.GetListAsync(pagerDto);
            return result;
        }
复制代码

如果是高级查询,我们则是根据传入分页查询对象的属性在高级查询对话框中进行赋值,然后获得对象后进行查询获得记录的。

 

在代码生成工具中,我们根据实际项目的代码,定义好对应的模板文件,如下所示。

最后在生成代码的时候,整合这些NVelocity的模板文件,根据表对象的信息,生成对应的文件供我们开发使用即可。

复制代码
            #region Model 实体部分

            string entityTemplateFile = ProjectPath + "Templates/Entity.cs.vm";
            var entityAdapter = new SugarEntityAdapter(databaseInfo, selectedTableNames, entityTemplateFile);
            entityAdapter.DirectoryOfOutput = mainSetting.RootNameSpace + "/Core/Modal";
            entityAdapter.Execute();

            #endregion

            #region Interface部分和Application部分

            var appInterface = new SugarServiceInterfaceAdapter(databaseInfo, selectedTableNames, ProjectPath + "Templates/IService.cs.vm", databaseTypeName);
            appInterface.DirectoryOfOutput = mainSetting.RootNameSpace + "/Core/Interface";
            appInterface.Execute();

            var appService = new SugarServiceAdapter(databaseInfo, selectedTableNames, ProjectPath + "Templates/Service.cs.vm", databaseTypeName);
            appService.DirectoryOfOutput = mainSetting.RootNameSpace + "/Core/Service";
            appService.Execute();

            #endregion

            #region Web API Controller 部分

            var controller = new SugarControllerAdapter(databaseInfo, selectedTableNames, ProjectPath + "Templates/Controller.cs.vm", databaseTypeName);
            controller.DirectoryOfOutput = mainSetting.RootNameSpace + "/Controller";
            controller.Execute();

            #endregion
复制代码

 

系列文章:

基于SqlSugar的开发框架的循序渐进介绍(1)–框架基础类的设计和使用

基于SqlSugar的开发框架循序渐进介绍(2)– 基于中间表的查询处理

基于SqlSugar的开发框架循序渐进介绍(3)– 实现代码生成工具Database2Sharp的整合开发

sql server 性能调优之 死锁排查 - 花阴偷移 - 博客园

mikel阅读(643)

来源: sql server 性能调优之 死锁排查 – 花阴偷移 – 博客园

一.概述

记得以前客户在使用软件时,有偶发出现死锁问题,因为发生的时间不确定,不好做问题的重现,当时解决问题有点棘手了。现总结下查看死锁的常用二种方式。

1.1 第一种是图形化监听:

SQLServer –>工具–> SQL server profiler   登录后在跟踪属性中选择如下图:

监听到的死锁图形如下图

这里的描述大致是:有二个进程 一个进程ID是96, 另一个ID是348.   系统自动kill 掉了进程ID:96,保留了进程ID:348 的事务Commit。

上面死锁是由于批量更新出现PAG范围锁, 双方进程在同一分区索引资源上。ID96,348都请求想获取更新锁(U),各占排它锁(x)不释放,直到锁超时。

 

1.2 第二种是使用日志跟踪(errorlog)

以全局方式打开指定的跟踪标记

DBCC TRACEON(1222,-1)
DBCC TRACEON(1204,-1)

使用  EXEC master..xp_readerrorlog 查看日志。 由于记录的死锁信息太多,贴出几个重点说下(红色加粗表示)

复制代码
Deadlock encountered .... Printing deadlock information
Wait-for graph
NULL
Node:1 
PAGE: 7:1:6229275 CleanCnt:2 Mode:IX Flags: 0x3
Grant List 3:
Owner:0x00000004E99B7880 Mode: IX Flg:0x40 Ref:1 Life:02000000 SPID:219 ECID:0 XactLockInfo: 0x0000000575C7E970
SPID: 219 ECID: 0 Statement Type: UPDATE Line #: 84
Input Buf: Language Event: exec proc_PUB_StockDataImport
Requested by: 
ResType:LockOwner Stype:'OR'Xdes:0x0000000C7A905D30 Mode: U SPID:64 BatchID:0 ECID:59 TaskProxy:(0x0000000E440AAFE0) Value:0x8d160240 Cost:(0/0)
NULL

Node:2 
PAGE: 7:1:5692366 CleanCnt:2 Mode:U Flags: 0x3
Grant List 3:
Owner:0x0000000D12099B80 Mode: U Flg:0x40 Ref:0 Life:00000001 SPID:64 ECID:0 XactLockInfo: 0x000000136B4758F0
SPID: 64 ECID: 0 Statement Type: UPDATE Line #: 108
Input Buf: RPC Event: Proc [Database Id = 7 Object Id = 907150277]
复制代码

——– node:1 部分显示的几个关键信息:

PAGE 7:1:6229275  (所在数据库ID 7, 1分区, 6229275行数)

Mode: IX  锁的模式  意向排它锁

SPID: 219  进程ID

Event: exec proc_PUB_StockDataImport  执行的存储过程名

——-node:2 部分显示的几个关键信息

PAGE 7:1:5692366  (所在数据库ID 7, 1分区,5692366行数)

Mode:U 锁的模式  更新锁

RPC Event: Proc 远程调用

SPID: 64  进程ID

复制代码
Victim Resource Owner:
ResType:LockOwner Stype:'OR'Xdes:0x0000000C7A905D30 Mode: U SPID:64 BatchID:0 ECID:59 TaskProxy:(0x0000000E440AAFE0) Value:0x8d160240 Cost:(0/0)
deadlock-list
deadlock victim=process956f4c8
process-list
process id=process956f4c8 taskpriority=0 logused=0 waitresource=PAGE: 7:1:6229275 waittime=2034 ownerId=2988267079 transactionname=UPDATE 
lasttranstarted=2018-04-19T13:54:00.360 XDES=0xc7a905d30 lockMode=U schedulerid=24 kpid=1308 status=suspended spid=64 sbid=0 ecid=59 priority=0 trancount=0 
lastbatchstarted=2018-04-19T13:53:58.033 lastbatchcompleted=2018-04-19T13:53:58.033 clientapp=.Net SqlClient Data Provider hostname=VMSERVER76 hostpid=16328 
isolationlevel=read committed (2) xactid=2988267079 currentdb=7 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=Test.dbo.proc_CnofStock line=108 stmtstart=9068 stmtend=9336 sqlhandle=0x03000700c503123601ba25019ca800000100000000000000
update dbo.pub_stock
set UpdateTime=GETDATE()
from pub_stock a
join PUB_PlatfromStocktemp b on a.GUID=b.StockGuid
复制代码

从上面的信息能看到kill 掉的是进程id是process956f4c8,

    进程spid=64

lockMode=U 获取更新锁

isolationlevel=read committed

executionStack 执行的堆信息:

                  存储名  procname=Test.dbo.proc_CnofStock

                  语句    update dbo.pub_stock set UpdateTime=GETDATE()   ..

clientapp   发起事件的来源

 

1.3 最后总结   避免死锁的解决方法

按同一顺序访问对象。

优化索引,避免全表扫描,减少锁的申请数目.

避免事务中的用户交互。

使用基于行版本控制的隔离级别。

将事务默认隔离级别的已提交读改成快照

SET TRANSACTION ISOLATION LEVEL SNAPSHOT

使用nolock去掉共享锁,但死锁发生在u锁或x锁上,则nolock不起作用

升级锁颗粒度(页锁,表锁), 以阻塞还代替死锁

sql server 性能调优之 资源等待 LCk - 花阴偷移 - 博客园

mikel阅读(644)

来源: sql server 性能调优之 资源等待 LCk – 花阴偷移 – 博客园

 一.  概述

这次介绍实例级别资源等待LCK类型锁的等待时间,关于LCK锁的介绍可参考 “sql server 锁与事务拨云见日”。下面还是使用sys.dm_os_wait_stats 来查看,并找出耗时最高的LOK锁。

复制代码
select wait_type,
waiting_tasks_count,
wait_time_ms ,
max_wait_time_ms,
signal_wait_time_ms
from sys.dm_os_wait_stats
where wait_type like 'LCK%' 
order by  wait_time_ms desc
复制代码

查出如下图所示:

  1.  分析介绍

重点介绍几个耗时最高的锁含义:

LCK_M_IX: 正在等待获取意向排它锁。在增删改查中都会有涉及到意向排它锁。
LCK_M_U: 正在等待获取更新锁。 在修改删除都会有涉及到更新锁。
LCK_M_S:正在等待获取共享锁。 主要是查询,修改删除也都会有涉及到共享锁。
LCK_M_X:正在等待获取排它锁。在增删改中都会有涉及到排它锁。
LCK_M_SCH_S:正在等待获取架构共享锁。防止其它用户修改如表结构。
LCK_M_SCH_M:正在等待获取架构修改锁 如添加列或删除列 这个时候使用的架构修改锁。

下面表格是统计分析

锁类型 锁等待次数 锁等待总时间(秒) 平均每次等待时间(毫秒) 最大等待时间
LCK_M_IX 26456 5846.871 221 47623
LCK_M_U 34725 425.081 12 6311
LCK_M_S 613 239.899 391 4938
LCK_M_X 4832 77.878 16 4684
LCK_M_SCH_S 397 77.832 196 6074
LCK_M_SCH_M 113 35.783 316 2268

  注意: wait_time_ms 时间里,该时间表包括了signal_wait_time_ms信号等待时间,也就是说wait_time_ms不仅包括了申请锁需要的等待时间,还包括了线程Runnable 的信号等待。通过这个结论也能得出max_wait_time_ms 最大等待时间不仅仅只是锁申请需要的等待时间。

 

2. 重现锁等待时间

--  重置
DBCC SQLPERF ('sys.dm_os_wait_stats', CLEAR);

--  会话1 更新SID=92525000, 未提交
begin tran 
update [dbo].[PUB_StockTestbak] set model='mmtest' where sid=92525000
-- 会话2 查询该ID, 由于会话1更新未提交 占用x锁,这里查询将阻塞
select * from [PUB_StockTestbak] where sid=92525000

手动取消会话2的查询,占用时间是61秒,如下图:

再来统计资源等待LCK,如下图 :

总结:可以看出资源等待LCK的统计信息还是非常正确的。所以找出性能消耗最高的锁类型,去优化是很有必要。比较有针对性的解决阻塞问题。

3. 造成等待的现象和原因

现象:

(1)  用户并发越问越多,性能越来越差。应用程序运行很慢。

(2)  客户端经常收到错误 error 1222 已超过了锁请求超时时段。

(3)  客户端经常收到错误 error 1205 死锁。

(4)  某些特定的SQL 不能及时返回应用端。

原因:

(1) 用户并发访问越多,阻塞就会越来越多。

(2) 没有合理使用索引,锁申请的数量多。

(3) 共享锁没有使用nolock, 查询带来阻塞。 好处是必免脏读。

(4) 处理的数据过大。比如:一次更新上千条,且并发多。

(5) 没有选择合适的事务隔离级别,复杂的事务处理等。

4.  优化锁的等待时间

在优化锁等待优化方面,有很多切入点 像前几篇中有介绍 CPU和I/O的耗时排查和处理方案。 我们也可以自己写SQL来监听锁等待的SQL 语句。能够知道哪个库,哪个表,哪条语句发生了阻塞等待,是谁阻塞了它,阻塞的时间。

从上面的平均每次等待时间(毫秒),最大等待时间 作为参考可以设置一个阀值。 通过sys.sysprocesses 提供的信息来统计, 关于sys.sysprocesses使用可参考 “sql server 性能调优 从用户会话状态分析”。 通过该视图 监听一段时间内的阻塞信息。可以设置每10秒跑一次监听语句,把阻塞与被阻塞存储下来。

思想如下:

复制代码
-- 例如 找出被阻塞会话ID 如时间上是2秒 以及谁阻塞了它的会话ID
SELECT spid,blocked #monitorlock FROM sys.sysprocesses 
where blocked>0 and    waittime>2000 

-- 通过while或游标来一行行获取临时表的 会话ID,阻塞ID,通过exec动态执行来获取sql语句文本 进行存储
exec('DBCC INPUTBUFFER('+@spid+')') 

exec('DBCC INPUTBUFFER('+@blocked+')')
复制代码