月度归档:2023年11月

SQL提高查询性能的几种方式

创建索引,提高性能

索引可以极大地提高查询性能,其背后的原理:

  1. 索引是的数据库引擎能够快速的找到表中的数据,它们类似于书籍的目录,使得你不需要逐页查找所需要的信息
  2. 索引能够帮助数据库引擎直接定位到所需的数据,从而大大减少磁盘I/O操作,如果没有索引,SQL SERSER可能需要执行全表的扫描来查询数据,这需要大量的磁盘I/O操作
  3. 在分布式查询中,如果远程服务器上的表有索引,那么只需要将所需要的数据行发送的请求服务器,而不是整个表,从而减少了网络的流量
  4. 查询优化器会使用索引统计信息来生成最有效的查询计划。

SQL Server 提供了多种类型的索引,以优化查询性能和满足不同的数据访问需求,以下是一些主要常用的索引类型:

  1. 聚集索引:每个表只能有一个聚集索引。这种索引决定了表中数据的物理存储顺序。聚集索引使用行的键值对数据进行排序和存储.
    CREATE CLUSTERED INDEX IDX_Table_Column ON Table (Column);
  2. 非聚集索引:非聚集索引与聚集索引不同,它不影响数据的物理存储顺序,而是创建一个不同的数据结构(B-tree),其中包含键值和对应行数据的指针。一个表可以有多个非聚集索引。
   CREATE NONCLUSTERED INDEX IDX_Table_Column 
   ON Table (Column);
  1. 唯一索引:唯一索引确保索引键中的每个值只出现一次。这意味着每个索引键对应一个唯一的数据行。唯一索引可以是聚集索引或非聚集索引。
   CREATE UNIQUE NONCLUSTERED INDEX IDX_Table_Column 
   ON Table (Column);
  1. 复合索引:复合索引是包含两个或更多列的索引。复合索引的顺序很重要,因为 SQL Server 将首先按照第一列排序,然后在每个第一列的值内按照第二列排序,依此类推。
   CREATE INDEX IDX_Table_Column1_Column2 
   ON Table (Column1, Column2);
  1. 过滤索引:过滤索引是非聚集索引的一种变体,它只包含满足特定过滤谓词的行。这可以减小索引的大小并提高查询性能。
    CREATE NONCLUSTERED INDEX IDX_Table_Column ON Table (Column) WHERE Column IS NOT NULL;
  2. 全文索引:全文索引用于在全文查询中快速查找文本数据中的词语。
   CREATE FULLTEXT INDEX ON Table (TextColumn) 
   KEY INDEX IDX_Table_Column;

避免在WHERE子句中使用NOT和<>运算符,提高性能

在SQL Server查询中,尽量避免在WHERE子句中使用NOT和<>运算符的主要原因是这两种运算符可能会降低查询性能。以下是具体的解释:

  1. 索引不利用: SQL Server通常会使用索引来加速查询。但是,当你使用NOT或<>运算符时,SQL Server可能无法有效地使用索引,因为这些运算符需要扫描所有的行而不只是索引的一部分。这可能导致查询速度变慢。
  2. 全表扫描: 当使用NOT或<>运算符时,SQL Server可能需要执行全表扫描,即需要检查表中的每一行以确定哪些行满足查询条件。全表扫描通常比使用索引扫描要慢得多。
  3. 结果预测困难: 对于优化器来说,预测使用NOT或<>运算符的查询结果的行数比较困难,这可能会导致生成的执行计划不是最优的。
    因此,尽管在某些情况下,使用NOT或<>运算符是必要的,但在可能的情况下,应尽量避免使用它们,以提高查询性能。

在某些情况下,我们可以通过其他查询语句来避免使用”NOT”和”<>”运算符达到同样的结果,这可能有助于SQL SERVER更有效地使用索引,从而提高查询性能

  1. 使用 = 和 IN 运算符: 如果你知道你想要查询的具体值,你可以使用 = 或 IN 运算符,而不是使用 <>。例如,如果你想要查询所有不是 ‘A’ 或 ‘B’ 的行,你可以将查询从 WHERE column <> ‘A’ AND column <> ‘B’ 改写为 WHERE column IN (‘C’, ‘D’, ‘E’, …)
  2. 使用 BETWEEN 运算符: 如果你想要查询的值在一个范围内,你可以使用 BETWEEN 运算符,而不是使用 <>。例如,如果你想要查询所有不在1到10之间的行,你可以将查询从 WHERE column NOT BETWEEN 1 AND 10 改写为 WHERE column < 1 OR column > 10。
  3. 使用 IS NULL 和 IS NOT NULL: 如果你想要查询的是空值或非空值,你可以使用 IS NULL 或 IS NOT NULL 运算符,而不是使用 <>。例如,如果你想要查询所有非空的行,你可以将查询从 WHERE column <> NULL 改写为 WHERE column IS NOT NULL。
  4. 使用EXISTS和NOT EXISTS:特别是在处理相关子查询时,EXISTS和NOT EXISTS在某些情况下可能比使用NOT和<>运算符更高效。

对于存储大数据集时,将表变量改为临时表,提高性能

表变量和临时表都是用于在SQL Server中存储一些临时数据的工具。它们之间存在一些关键的区别,包括在性能方面的差异。

表变量

表变量在SQL Server中被定义为一个变量,这意味着它的生命周期只在声明它的批处理或存储过程中。表变量通常用于存储返回不多的数据,例如几百行。
性能方面:

  1. 表变量不会导致重新编译,因此在某些情况下,它可以提高性能。
  2. 表变量不会在磁盘上创建,而是在内存中创建,通常可以提供更好的性能。
  3. 表变量不会参与事务,因此不会导致锁定和日志记录,这可能会提高性能。
    创建表变量,如下所示
DECLARE @TableVariable TABLE
(
    ID INT,
    Value NVARCHAR(50)
)

临时表

临时表在SQL Server中被定义为一个真正的表,存储在tempdb数据库中,并且可以在当前会话中使用。临时表通常用于存储大量数据,例如数千或数万行。
性能方面:

  1. 临时表可能会导致存储过程的重新编译,这可能会降低性能。
  2. 临时表在磁盘上创建,这可能会比在内存中创建表变量慢。
  3. 临时表参与事务,可能会导致锁定和日志记录,这可能会降低性能。
    创建临时表,如下所示
CREATE TABLE #TempTable
(
    ID INT,
    Value NVARCHAR(50)
)

总的来说,表变量和临时表各有优势,选择哪种类型取决于你的特定需求。如果你需要存储大量数据,或者需要使用索引、统计信息等功能,那么临时表可能是更好的选择。如果你只需要存储少量数据,并且希望避免重新编译和日志记录,那么表变量可能是更好的选择。

使用 OPTION(RECOMPILE),提高性能

在 SQL Server 中,OPTION (RECOMPILE) 是一种查询提示,它会使 SQL Server 在每次运行查询时都生成一个新的执行计划。这在某些情况下可以帮助提高查询性能。以下是其背后的原理:

  1. 参数灵敏性:当查询因参数值的变化而表现出不同的性能特性时,OPTION (RECOMPILE) 可以提高性能。这是因为每次查询执行时,SQL Server 都会根据当前参数值生成一个新的执行计划。
  2. 避免计划缓存问题:如果查询计划在缓存中占用大量空间,或者因为参数嗅探问题导致性能下降,那么 OPTION (RECOMPILE) 可以帮助解决这些问题。因为每次查询执行时,都会生成一个新的执行计划,而不是重用缓存中的旧计划。
  3. 数据修改操作:对于那些涉及大量数据修改的查询(如 INSERT、UPDATE、DELETE),使用 OPTION (RECOMPILE) 可以帮助 SQL Server 生成一个更优的执行计划,因为它会考虑到最新的数据分布。

以下是一个使用 OPTION (RECOMPILE) 的例子
假设我们有一个名为 Employees 的表,我们想要根据 salary 列的值来获取一些记录。我们可能会创建一个存储过程来执行这个查询,如下所示:

CREATE PROCEDURE GetEmployees @Salary INT
AS
BEGIN
    SELECT * FROM Employees WHERE Salary > @Salary
END

在这个存储过程中,SQL Server 会为第一次运行存储过程时的 @Salary 参数值生成一个执行计划。然后,对于后续的运行,它会重用这个执行计划,无论 @Salary 参数的值是多少。现在,假设 Employees 表中的 Salary 分布是不均匀的,有些薪水范围的员工数量远多于其他薪水范围。在这种情况下,为某个特定的 @Salary 值生成的执行计划可能对其他 @Salary 值并不是最优的。为了解决这个问题,我们可以在查询中使用 OPTION (RECOMPILE),如下所示:

CREATE PROCEDURE GetEmployees @Salary INT
AS
BEGIN
    SELECT * FROM Employees WHERE Salary > @Salary OPTION (RECOMPILE)
END

现在,每次运行存储过程时,SQL Server 都会为当前的 @Salary 参数值生成一个新的执行计划,这可以提高查询性能。

然而,需要注意的是,OPTION (RECOMPILE) 并不总是提高性能。因为每次查询执行时都生成新的执行计划会消耗CPU资源,所以如果查询非常频繁,可能会导致CPU资源的浪费。因此,建议在使用 OPTION (RECOMPILE) 时,应根据具体的查询和系统性能来进行权衡。

总结

以上是我工作时常使用提高性能的几种方法,性能优化是一个持续不断的过程,它需要我们在实践中不断地学习,尝试和改进。而且,每个数据库和每个查询都有其独特性,所以最有效的优化策略可能因情况而异。如果你们有更多的方法、技巧或者是实践经验,希望你们能在评论区分享哦。让我们一起在这个领域里进一步深化我们的知识,共同提高我们的技能。在这个过程中,我期待与你们的交流和学习,让我们一起在SQL查询性能优化的道路上不断前行。

作者:百宝门-后端组-李桂林

Blazor入门教程

Blazor简介

Blazor 是由Microsoft开发的一款基于.NET的开源交互式Web UI框架。Blazor使开发人员能够使用C#和HTML建立全堆栈的单页应用程序,并避免使用JavaScript。Blazor基于组件模型,该模型提供了以具有强类型的符合Razor标准的页面和组件的形式构建用户界面的能力。

Blazor的加入使得.NET开发人员有机会在客户端和服务器上使用同一种编程模型,同时享受到.NET的优势,比如其功能强大的运行时,标准库,语言互操作性和辅助开发者高效开发的工具等。

在Blazor中,有两个主要的托管模型:

  • ** Blazor Server: 在此模式下,应用程序在服务器端运行,并使用SignalR实时通讯框架与浏览器进行交互。这种模型要求永久的有效连接,但是客户端的计算和下载需求会大大减低,所有的逻辑运行都在服务器端。
  • ** Blazor WebAssembly: 在此模式下,应用程序直接在客户端的WebAssembly中运行,允许C#代码在浏览器中执行,不依赖服务器。

开发要求

可使用最新版本的 Visual Studio 2022、Visual Studio for Mac 或 Visual Studio Code 来生成 Blazor 应用。
本文使用 Visual Studio 2022.

无论使用哪种开发环境,都需要安装 .NET 6.0(或更高版本)SDK。 如果要使用 Visual Studio 2022,则需要包含“ASP.NET 和 Web 开发”工作负载。 安装后,即可开始生成 Blazor 应用。

创建应用

在这里,我们创建一个Blazor Server 模式的程序。

1. 启动 Visual Studio 2022 并选择“Create a new project”。

2. 在“Create a new project”窗口中,在搜索框中键入 Blazor,然后按 Enter。

3. 选择“Blazor Server 应用”模板并选择“下一步”。

4. 在“Configure new project”窗口中,输入 BlazorApp 作为项目名称,然后选择“下一步”。

5. 在“Additional information”窗口中,如果尚未选择,则在“框架”下拉列表中选择“.NET 7.0 (标准期限支持)”,然后单击“Create”按钮。

使用解决方案资源管理器查看项目内容。

Program.cs 是启动服务器以及在其中配置应用服务和中间件的应用的入口点。

App.razor 为应用的根组件。

Pages 目录包含应用的一些示例网页。

BlazorApp.csproj 定义应用项目及其依赖项,且可以通过双击解决方案资源管理器中的 BlazorApp 项目节点进行查看。

Properties 目录中的 launchSettings.json 文件为本地开发环境定义不同的配置文件设置。创建项目时会自动分配端口号并将其保存在此文件上。

6. 运行应用

单击 Visual Studio 调试工具栏中的“开始调试”按钮(绿色箭头)以运行应用。
Alt text

首次在 Visual Studio 中运行 Web 应用时,它将设置用于通过 HTTPS 托管应用的开发证书,然后提示你信任该证书。建议同意信任该证书。证书将仅用于本地开发,如果没有证书,大多数浏览器都会对网站的安全性进行投诉。

等待应用在浏览器中启动。转到以下页面后,你已成功运行第一个 Blazor 应用!
Alt text

显示的页面由位于 Pages 目录内的 Index.razor 文件定义。其内容如下所示:

  • Pages/Index.razor
@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

它已包含将其设置为主页并显示文本 Hello, world! 和 Welcome to your new app 的代码。它还包含一个 SurveyPrompt 组件,可呈现指向 Blazor 反馈调查的链接。

在 Blazor 中生成 UI 时,通常会在同一文件中将静态 HTML 和 CSS 标记与 C# 代码混用。 若要区分这些类型的代码,可使用 Razor 语法。 Razor 语法包括前缀为 @ 符号的指令,这些指令用于分隔 C# 代码、路由参数、绑定数据、导入的类以及其他特性。

  • @page 指令:该指令为 Blazor 提供路由模板。 在运行时,Blazor 通过将此模板与用户请求的 URL 相匹配来查找要呈现的页面。 在本例中,它可能与 http://yourdomain.com/index 形式的 URL 匹配。

试用计数器

在正在运行的应用中,通过单击左侧边栏中的“Counter”选项卡导航到“Counter”页。然后应显示以下页面:
Alt text

选择“Click me”按钮,在不刷新页面的情况下递增计数。若要在网页中递增计数器,通常需要编写 JavaScript,但对于 Blazor,你可以使用 C#。

可以在 Pages 目录内的 Counter.razor 文件处找到 Counter 组件的实现。

  • Pages/Counter.razor
@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>


@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}
  • @code 指令:该指令声明以下块中的文本是 C# 代码。 你可以根据需要将任意数量的代码块置于组件中。 你可以在这些代码块中定义组件类成员,并通过计算、数据查找操作或其他源设置其值。 在本例中,代码定义了一个名为 currentCount 的组件成员并设置了值。
  • 成员访问指令:如果要在呈现逻辑中包含成员的值,请使用 @ 符号,后跟 C# 表达式,例如成员的名称。 在本例中,@currentCount 指令用于在 标记中呈现 currentCount 成员的值。

在浏览器中对于 /counter 的请求(由位于顶部的 @page 指令指定)导致 Counter 组件呈现其内容。

每次点击“Click Me”按钮时:
触发 onclick 事件。
调用 IncrementCount 方法。
currentCount 为递增。
呈现该组件以显示更新的计数。

7. 添加组件

每个 razor 文件都会定义一个可重复使用的 UI 组件。

在 Visual Studio 中打开 Index.razor 文件。Index.razor 文件已存在,并且是在创建项目时创建的。它位于之前创建的 BlazorApp 目录中的 Pages 文件夹中。

通过在 Index.razor 文件末尾添加 元素,向应用主页添加 Counter。

  • Pages/Index.razor
@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<Counter />

单击“热重载”按钮,以将更改应用到正在运行的应用。然后 Counter 组件将出现在主页上。
Alt text

8. 修改组件

组件可以有参数,组件参数使用特性或子内容指定,这允许在子组件上设置属性。在 Counter 组件上定义参数,以指定每次点击按钮时的增量:

使用 [Parameter] 属性为 IncrementAmount 添加公共属性。
将 IncrementCount 方法更改为在递增 currentCount 值时使用 IncrementAmount。
下面的代码演示了怎样实现此目的。

  • Pages/Counter.razor
@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    [Parameter]
    public int IncrementAmount { get; set; } = 1;

    private void IncrementCount()
    {
        currentCount += IncrementAmount;
    }
} 

在 Index.razor 中,更新 元素以添加IncrementAmount 属性,该属性会将增量更改为 10,如以下代码中突出显示的行所示:

  • Pages/Index.razor
@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<Counter IncrementAmount="10" /> 

通过单击“热重载”按钮将更改应用。Index 组件现在具有自己的计数器,每次点击“Click me”按钮时,该计数器会递增 10,如下图所示。(/counter 处的 Counter 组件(Counter.razor)将继续按 1 递增。)
Alt text

恭喜你已生成并运行首个 Blazor 应用!

9. 数据绑定和事件

在 Razor 组件中,可以将 HTML 元素数据绑定到 C# 字段、属性和 Razor 表达式值。 数据绑定支持在 HTML 和 Microsoft .NET 之间进行双向同步。

呈现组件时,数据从 HTML 推送到 .NET。 组件在事件处理程序代码执行后呈现自身,这就是为什么在触发事件处理程序后,属性更新会立即反映在 UI 中。

可使用 @bind 标记将 C# 变量绑定到 HTML 对象。 按名称将 C# 变量定义为 HTML 中的字符串。 在下面的练习中,可以看到数据绑定的示例。

创建 Todo 项

在项目的根目录(BlazorApp 文件夹)中创建一个名为 TodoItem.cs 的新文件,用于保存表示待办事项的 C# 类。

为 TodoItem 类使用以下 C# 代码。 通过使用 ? 将 Title 声明为可为空字符串。

public class TodoItem
{
    public string? Title { get; set; }
    public bool IsDone { get; set; } = false;
}

绑定 TodoItem 列表

现可在 Blazor 中将 TodoItem 对象集合绑定到 HTML。 若要绑定这些对象,请在 Pages/Index.razor 文件中进行以下更改:

在 @code 块中为待办项添加一个字段。 Todo 组件使用此字段来维护待办项列表的状态。
添加无序列表标记和 foreach 循环,以将每个待办项呈现为列表项 ()。

@page "/"

...

<ul>
    @foreach (var todo in todos)
    {
        <li>
            <input type="checkbox" @bind="todo.IsDone" />
            <input @bind="todo.Title" />
        </li>
    }
</ul> 

@code {

    ...

    private List<TodoItem> todos = new() { new TodoItem() {Title = "Item1", IsDone = true}, new TodoItem() {Title = "Item2", IsDone = false}};
}

动态创建元素

  • 在列表 li 下方添加一个文本输入 (input) 和一个按钮 (button)。
  • 添加 AddTodo 方法,并使用 @onclick 属性来为按钮注册方法。 点击按钮时,会调用 AddTodo C# 方法。
  • 在@code中增加AddTodo 方法,将具有指定标题的 TodoItem 添加到列表。 通过将 newTodo 设置为空字符串来清除文本输入的值。
@page "/"
...
<input placeholder="Something todo" @bind="newTodo" />
<button @onclick="AddTodo">Add todo</button>

ul>
    @foreach (var todo in todos)
    {
        <li>
            <input type="checkbox" @bind="todo.IsDone" />
            <input @bind="todo.Title" />
        </li>
    }
</ul> 

@code {    
    ...
    private List<TodoItem> todos = new() { new TodoItem() { Title = "Item1", IsDone = true }, new TodoItem() { Title = "Item2", IsDone = false } };

    private string? newTodo;

    private void AddTodo()
    {
        if (!string.IsNullOrWhiteSpace(newTodo))
        {
            todos.Add(new TodoItem { Title = newTodo });
            newTodo = string.Empty;
        }
    }
}

点击 Add todo 查看效果
Alt text

总结

Blazor是一款强大的Web开发框架,它为.NET开发者开辟了通往前端开发的新道路。通过使用Blazor,你可以运用你的C#和.NET技能进行全栈开发,这降低了学习入口和复杂性。

我们在本教程中接触到了Blazor的主要特性与概念,比如组件化、数据绑定和事件以及两种运行模式。你已经明白了如何用Blazor创建单页应用,以及Blazor与其他流行前端框架的差异。希望这些知识能够为你提供一个清晰的框架,帮助你理解Blazor的优势并决定是否在你的下个项目中使用它。

但是,记住我们只触及了表面;Blazor提供了更多的深度和复杂性等待你去探索。为了深入了解Blazor,你可以研究更复杂的例子,尝试使用Blazor去创建更实际的应用,或者深挖Blazor的文档以了解它的更多特性和优点。

总的来说,Blazor是.NET开发者的一种优秀选择,它扩展了.NET生态系统,使得它更全面,更具可达性。Blazor最大的魅力在于它拓宽了.NET开发者的视野,让开发者不再局限后端或桌面应用,前端的世界同样可以用熟悉的语言和工具去进行开发。

源代码地址: https://github.com/DXG88/BlazorApp