Github上优秀的.NET Core项目 - lingfeng95 - 博客园

mikel阅读(972)

来源: Github上优秀的.NET Core项目 – lingfeng95 – 博客园

Github上优秀的.NET Core开源项目的集合。内容包括:库、工具、框架、模板引擎、身份认证、数据库、ORM框架、图片处理、文本处理、机器学习、日志、代码分析、教程等。

Github地址:https://github.com/jasonhua95/awesome-dotnet-core ,【awesome-dotnet-core】

其中的翻译有可能有问题,大家发现了及时提出来,其他的比较好的项目也可以提出来,我会及时添加修改上去的。

一般

框架, 库和工具

API

  • autorest – Swagger(OpenAPI)规范代码生成器,具有C#和Razor模板。支持C#,Java,Node.js,TypeScript,Python和Ruby。
  • aspnet-api-versioning – 提供一组库,这些库可将服务API版本添加到ASP.NET Web API,具有ASP.NET Web API的OData和ASP.NET Core。
  • AspNetCoreRateLimit – ASP.NET限速中间件。
  • CondenserDotNet – 使用Kestrel和Consul的API Condenser / Reverse Proxy,包括轻量级consul库。
  • Flurl – 适用于.NET的Fluent URL构建器和可测试的HTTP。
  • GraphQL
    • Dapper.GraphQL – 一个旨在将Dapper和graphql-dotnet项目集成在一起的库,主要考虑的是易用性和性能。
    • graphql-aspnetcore – ASP.NET Core MiddleWare创建GraphQL端点。
    • graphql-convention – 该库是GraphQL的补充层,使您可以使用现有的属性和方法作为字段解析器,将.NET类自动包装到GraphQL模式定义中。
    • graphiql-dotnet – 用于ASP.NET Core的GraphiQL中间件。
    • graphql-dotnetcore – 基于graphql-js的.NETQL GraphQL。
    • graphql-dotnet – GraphQL for .NET。
    • graphql-dotnet-server – GraphQL for .NET – 订阅传输WebSockets。
    • Hot Chocolate – .Net Core和.NET Framework的GraphQL服务器。
    • FSharp.Data.GraphQL – Facebook GraphQL查询语言的FSharp实现。
    • parser – .NET中GraphQL的词法分析器和解析器。
    • tanka-graphql – GraphQL执行库和服务器库,支持SignalR,Apollo,模式操纵以及Apollo和graphql-js熟悉的其他功能。
  • halcyon – ASP.NET的HAL实现。
  • JSON API .NET Core – 用于ASP.Net Core的JSON API框架。
  • LightNode – 基于OWIN构建的Micro RPC / REST框架
  • NetCoreStack.Proxy – 适用于.NET Standard 2.0的类型安全的分布式REST库(NetCoreStack Flying Proxy)
  • NSwag – 用于.NET,Web API和TypeScript的Swagger / OpenAPI工具链。
  • OData – 开放数据协议(OData)支持创建基于HTTP的数据服务,允许使用统一资源标识符识别资源( URIs)并在抽象数据模型中定义,由Web客户端使用简单的HTTP消息进行发布和编辑。
  • OpenAPI Generator – 可以通过 OpenAPI Generator,在给定 OpenAPI 规范(v2, v3)的情况下自动生成 API 客户端库、server stubs、文档以及配置。
  • refit – 适用于.NET Core,Xamarin和.NET的自动类型安全REST库。
  • RestClient.Net – 适用于所有C#跨平台的REST客户端。
  • RestEase – 易于使用的类型安全REST API客户端库,简单且可自定义。
  • RestLess – .Net Standard的自动类型安全无反射REST API客户端库。
  • Restier – RESTier是一个RESTful API开发框架,用于在.NET平台上构建基于OData V4的标准化RESTful服务。
  • Restsharp – 用于.NET的简单REST和HTTP API客户端
  • Swashbuckle – Swagger工具,生成API文档,包括用于测试的UI。
  • WebAPIContrib for ASP.NET CORE – ASP.NET Core的附加组件和扩展库。

应用程序框架

  • ASP.NET Boilerplate – ABP是一个通用的WEB应用程序框架和项目模板。
  • Abp vNext – 该项目是ABP Web应用程序框架的下一代。
  • AsyncEx – async / await的帮助程序库。
  • Aeron.NET – 高效可靠的UDP单播,UDP组播和IPC消息传输。
  • akka.net – Akka是一个基于scala语言的Actor模型库,旨在构建一套高并发、分布式、自动容错、消息驱动应用的工具集。
  • Aggregates.NET – Aggregates.NET是一个框架,可以帮助开发人员将优秀的NServiceBus和EventStore库集成在一起。
  • ASP.NET MVC – 官方WEB应用程序框架,MVC。
  • ASP.NET Core – ASP.NET Core是一个跨平台的.NET框架。
  • Butterfly Server .NET – 允许用最少的工作量构建实时Web应用程序,分布式追踪的服务器端库。
  • CAP – CAP是处理分布式事务的解决方案,还具有EventBus功能,它轻巧,易于使用且高效。
  • Carter – Carter是一个路由框架,使代码更加简单明确。
  • Chromely – Electron.NET的轻量级替代品,构建HTML5桌面应用程序框架。
  • Cinchoo ETL – 用于.NET的ETL框架(用于CSV,Flat,Xml,JSON,键值对格式文件的分析器/写入器)。
  • CQRSlite – 用于帮助在C#中编写CQRS和Eventsourcing应用程序的轻量级框架。
  • dataaccess_aspnetcore – EF的UnitOfWork和Repositories的基类。
  • DNTFrameworkCore – DNTFrameworkCore 是一个轻量级且可扩展的基础结构,用于基于ASP.NET Core构建高质量的Web应用程序
  • DotNetCorePlugins – 用于动态加载.NET Core程序集,将其作为主应用程序的扩展来执行与Assembly.LoadFrom不同。
  • DotnetSpider – DotnetSpider,一个类似于WebMagic和Scrapy的.NET标准爬虫库。它是轻量级,高效且快速的高级Web爬网和抓取框架。
  • DotNetty – netty端口,事件驱动的异步网络应用程序框架。
  • dotvvm – Web应用程序的开源MVVM框架。
  • ElectronNET – 使用ASP.NET NET Core构建跨平台桌面应用程序。
  • EmbedIO – 一个小型的,跨平台,基于模块的Web服务器。
  • Ether.Network – Ether.Network是一个开源网络库,允许开发人员通过sockets创建简单,快速和可扩展的套接字服务器或客户端的基本库。
  • EventFlow – EventFlow是一个易于使用的基本CQRS + ES框架。
  • ExcelDataReader – 用C#编写的轻量级快速库,用于读取Microsoft Excel文件。
  • ExtCore – 用于创建模块化和可扩展的Web应用程序框​​架。
  • Finbuckle.MultiTenant – Finbuckle.MultiTenant是ASP.NET Core的多租户库。它提供用于租户解析,每个租户应用程序配置和每个租户数据隔离的功能。
  • fission – Fission 是一个构建在 Kubernetes 之上的 FaaS框架。ission 利用Kubernetes 集群管理、调度、网络管理等,将容器编排功能留给 Kubernetes,而 Fission 就专注于 FaaS 特性。
  • grpc – 远程过程调用(RPC)为构建分布式应用程序和服务提供了有用的抽象,grpc库。
  • Halibut – 使用基于SSL的JSON-RPC的.NET安全通信框架。
  • MagicOnion – MagicOnion是一个实时网络引擎,如SignalR,Socket.io和RPC-Web API框架。
  • MassTransit – .NET分布式应用程序框架。
  • microdot – 一个开源的.NET微服务框架。
  • MoreLINQ – LINQ to Objects的扩展。
  • Nancy – 用于在.NET和Mono上构建基于HTTP的服务的轻量级框架。
  • opencvsharp – OpenCV的跨平台库。
  • orleans – Orleans是一个跨平台的,用于构建分布式应用程序框架
  • protoactor-dotnet – Golang和C#的快速分布式Actor。
  • resin – 面向文档的搜索引擎,具有列索引,多重集合查询,基于JSON的查询语言和HTTP API。
  • RService.io – 用于ASP.NET Core的轻量级REST服务框架
  • ServiceStack – ServiceStack是一个简单,快速,通用和高效的全功能Web和 Web服务框架。
  • Steeltoe OSS – 用于常见微服务模式的.NET工具包。
  • Strathweb.TypedRouting.AspNetCore – 一个在ASP.NET Core MVC项目中启用强类型路由的库。
  • Xer.Cqrs – 轻巧易用的CQRS + DDD库。
  • X.PagedList – 用于轻松分页ASP.NET / ASP.NET Core中任何IEnumerable / IQueryable的库。

应用程序模板

身份认证和授权

区块链

  • BTCPayServer – BTCPay Server是一个免费的开源加密货币支付处理器,它使您可以直接以比特币和山寨币接收支付,而无需任何费用,交易成本或中间商。
  • Meadow – 一个集成的以太坊实施和工具套件,专注于Solidity测试和开发。
  • NBitcoin – 用于.NET框架的综合比特币库。
  • NBlockchain – 用于构建支持区块链的应用程序的.NET标准库
  • NBXplorer – 比特币和NBitcoin资源管理器客户端。
  • NEO – 为智能经济打造的开放网络,Neo利用区块链技术。
  • Nethereum – 将以太坊的热爱带到.NET。
  • Nethermind – .NET Core以太坊客户端
  • StratisBitcoinFullNode – 简单且经济实惠的端到端解决方案,用于在.Net框架上开发,测试和部署本机C#区块链应用程序。
  • Trezor.Net – Trezor加密货币硬件钱包的跨平台C#库。
  • WalletWasabi – 注重隐私的比特币钱包。内置Tor,CoinJoin和硬币控制功能。

机器人

  • BotSharp – BotSharp是AI Bot平台构建者的开源机器学习框架。
  • NadekoBot – 用C#编写的开源,通用的Discord聊天机器人。
  • Telegram.Bot – Telegram Bot API客户端。
  • Funogram – F#Telegram Bot Api库。

自动部署

  • cake-build – 跨平台构建自动化系统。
  • Colorful.Console – 设置您的C#控制台输出样式!
  • dotnet-docker – 用于.NET Core和.NET Core Tools的基本Docker镜像。
  • Dockerize.NET – .NET Cli工具,用于将.NET Core应用程序打包到Docker映像中:“ dotnet dockerize”
  • FlubuCore – 跨平台构建和自动化部署系统,用C#代码构建项目,执行,部署脚本。
  • GitInfo – 来自MSBuild,C#和VB的Git和SemVer信息,一种MSBuild编译工具。
  • GitVersioning – 使用version.json文件生成的唯一版本标记程序集和程序包等,并包括用于非官方构建的git commit ID。
  • go-dotnet – .NET Core Runtime的PoC Go包装器。
  • Image2Docker – 将现有Windows应用程序工作,移植到Docker的PowerShell模块。
  • LocalAppVeyor – .NET Core全局工具,可将appveyor.yml部署AppVeyor到本地。
  • msbuild – Microsoft Build Engine是一个用于构建应用程序的平台。
  • Nuke – 跨平台构建自动化系统。
  • Opserver – Stack Exchange的监控系统。
  • vsts-agent – Visual Studio Team Services构建和发布代理。

css, js帮助工具

  • BundlerMinifier – Visual Studio扩展,让您可以配置JS,CSS和HTML文件的捆绑和缩小。
  • JavaScriptViewEngine – 用于在JavaScript环境中呈现标记的ASP.NET MVC ViewEngine。适用于React和Angular服务器端呈现。
  • Smidge – 用于ASP.NET Core的轻量级运行时CSS / JavaScript文件缩小,组合,压缩和管理库。
  • Web Markup Minifier – 包含一组标记最小化器的.NET库。该项目的目标是通过减少HTML,XHTML和XML代码的大小来提高Web应用程序的性能。

缓存

  • CacheManager – 用C#编写的.NET的开源缓存抽象层。它支持各种缓存提供程序并实现许多高级功能。
  • EasyCaching – 开源缓存库,包含基本用法和缓存的一些高级用法,可以帮助我们更轻松地处理缓存。
  • Faster – Microsoft的快速key,value存储库。
  • Foundatio – 用于构建分布式应用程序的可插入基础库。
  • Microsoft Caching – 用于内存缓存和分布式缓存的库。
  • Stack Exchange Redis – 用于.NET语言的高性能通用redis客户端(C#等)。

内容管理系统CMS

  • Awesome-CMS-Core – Awesome-CMS-Core是一个使用ASP.Net Core和ReactJS构建的开源CMS,考虑到模块分离问题并提供最新的技术趋势,如.Net Core,React,Webpack,SASS,后台作业,Message Queue。
  • Blogifier.Core – Blogifier是用ASP.NET Core编写的简单,美观,轻巧的开源博客。
  • Cofoundry – Cofoundry是一个可扩展且灵活的.NET Core CMS和应用程序框架,专注于代码优先开发。
  • CoreWiki – 一个简单的ASP.NET core wiki。
  • dasblog-core – DasBlog博客项目。
  • Lynicon – Lynicon CMS系统。
  • Miniblog – ASP.NET Core博客引擎。
  • NetCoreCMS – NetCoreCMS是使用ASP.Net Core 2.0 MVC开发的模块化主题支持的内容管理系统。
  • Orchard Core CMS – 在模块化和可扩展的应用程序框架之上使用ASP.NET Core构建的开源内容管理系统。
  • Piranha CMS – 用于ASP.NET核心和实体框架核心的轻量级且不显眼的开源CMS。
  • Platformus – 基于ASP.NET Core 1.0和ExtCore框架的免费,开源和跨平台的CMS。
  • SimpleContent – 用于ASP.NET Core的简单而灵活的内容和博客引擎,可以使用或不使用数据库。
  • Squidex – Squidex是一个开源的CMS,基于MongoDB,CQRS和事件。
  • Swastika I/O Core CMS – 基于SIOH框架的ASP.NET Core / Dotnet核心系统(例如CMS,电子商务,论坛,问题解答,CRM …)
  • Weapsy – 基于DDD和CQRS的开源ASP.NET核心CMS。它支持开箱即用的MSSQL,MySQL,SQLite和PostgreSQL。
  • Wyam – 模块化静态内容和静态站点生成器。
  • ZKEACMS – 视觉设计,通过拖放构建网站

代码分析和指标

  • awesome-static-analysis – 针对各种编程语言的静态分析工具,链接和代码质量检查器的精选列表。
  • Code Analysis
    • CodeFormatter – CodeFormatter是使用Roslyn来自动重写我们的代码格式。
    • DevSkim – DevSkim是IDE扩展和语言分析器的框架,可在开发人员编写代码时在开发环境中提供内联安全性分析。
    • RefactoringEssentials – Visual Studio扩展工具,支持分析和重构代码。
    • roslyn-analyzers – Roslyn分析器分析您的代码的样式,质量和可维护性,设计和其他问题。
    • StyleCopAnalyzers – StyleCop规则实现的.NET编译器平台。
  • Metrics
    • AppMetrics – 用于记录和报告应用程序中的指标。
    • Audit.NET – 一个可扩展的框架,用于审核.NET和.NET Core中的执行操作。
    • BenchmarkDotNet – 用于基准测试的强大.NET库。
    • coverlet – Coverlet是.NET的跨平台代码覆盖框架。
    • MiniCover – 跨平台代码覆盖工具
    • NBench – .NET应用程序的性能基准测试框架
    • Nexogen.Libraries.Metrics – 用于在.NET中收集应用程序指标并将其导出到Prometheus的库。
    • OpenCover – 代码覆盖工具(仅适用于WINDOWS OS)
    • PerformanceMonitor – .NET应用程序性能监视器。
    • prometheus-net – .NET指标,监视系统,检测应用程序的prometheus库。
    • Prometheus.Client – Prometheus客户端。

压缩

  • lz4net – 适用于所有.NET平台的超快速压缩算法。
  • sharpcompress – 完全管理的C#库,用于处理许多压缩类型和格式。

编译器

  • Fable – F#到JavaScript编译器。
  • fparsec – F#和C#的解析器组合库。
  • IL2C – IL2C-ECMA-335 CIL / MSIL到C语言的翻译器。
  • Mond – 用C#编写的动态类型脚本语言,带有REPL,调试器和简单的嵌入API。
  • peachpie – .NET的开源PHP编译器。
  • Pidgin – 用于C#的轻量级,快速且灵活的解析库,由Stack Overflow开发。
  • roslyn – Roslyn .NET编译器提供具有丰富代码分析API的C#和Visual Basic语言。
  • Sprache – 小型,友好的C#解析器框架。

密码

  • BCrypt.Net – BCrypt密码库。
  • BCrypt.NET-Core – 用于安全存储密码的BCrypt.NET库。
  • BouncyCastle PCL – Bouncy Castle Crypto包是加密算法和协议的库。
  • multiformats – 用于编码/解码Multihashes的库,它是一个“容器”,用于描述计算摘要的散列算法。
  • nsec – NSec是基于libsodium的.NET Core新加密库。
  • SecurityDriven.Inferno – 专业的加密库。

数据库

  • DBreeze – C#.NET MONO NOSQL(嵌入式键值存储)ACID多范例数据库管理系统。
  • JsonFlatFileDataStore – 简单的JSON平面文件数据存储,支持打字和动态数据。
  • LiteDB – LiteDB是一个小型,快速,轻量的NoSQL嵌入式数据库。
  • NoDb – 文档数据库,个人博客和网站以及小型小册子网站是不使用数据库的不错的选择。
  • marten – Postgresql作为.NET应用程序的文档数据库和事件存储的库。
  • StringDB – StringDB是一个模块化的键/值对档案数据库,旨在消耗少量的ram并生成少量的数据库。
  • yessql – 适用于任何RDBMS的.NET文档数据库。

数据库驱动程序

数据库工具库

  • DbUp – 可帮助您将更改部署到SQL Server数据库,跟踪已经运行的SQL脚本,并运行使数据库更新所需的更改脚本。
  • Evolve – 使用纯SQL脚本的简单数据库迁移工具。受到Flyway的启发。
  • EFCorePowerTools – EF工具库 – reverse engineering, migrations and model。
  • fluentmigrator – .NET的迁移框架,就像Ruby on Rails Migrations一样。
  • monitor-table-change-with-sqltabledependency – 获取有关记录表更改的SQL Server通知。
  • roundhouse – RoundhousE是用于.NET的数据库迁移实用程序,它使用sql文件和基于源代码控制的版本控制。
  • SharpRepository – SharpRepository是一个用C#编写的通用存储库,它包括对各种关系,文档和对象数据库的支持,包括Entity Framework,RavenDB,MongoDb和Db4o。 SharpRepository还包括Xml和InMemory存储库实现。
  • TrackableEntities.Core – 使用.NET Core跨服务边界进行更改跟踪。
  • Mongo.Migration – MongoDB的即时迁移库。

日期和时间

  • Exceptionless.DateTimeExtensions – DateTimeRange,工作日和各种DateTime,DateTimeOffset,TimeSpan扩展方法。
  • FluentDateTime – 允许您编写更清晰的DateTime表达式和操作。部分灵感来自Ruby DateTime Extensions。
  • nodatime – 日期和时间API库。

分布式计算

  • AspNetCore.Diagnostics.HealthChecks – HealthChecks企业级核心诊断程序。
    • BeatPulse – ASP.NET Core应用程序的活动状况,健康检查库。
  • Foundatio – 可插拔的,用于构建松耦合的分布式应用程序库。
  • Rafty – RAFT 的实现库。
  • Obvs – 一个可观察微服务总线的库,基于Rx的接口。
  • Ocelot – Ocelot创建的API网关。
  • OpenTracing -API和分布式跟踪工具。
  • Polly – Polly是一个.NET弹性和瞬态故障处理库,允许开发人员以流畅和线程安全的方式表达诸如重试,断路器,超时,隔离头和回退之类的策略。
  • ProxyKit – HTTP反向代理的工具包。

电子商务与支付

  • nopCommerce – 免费的开源电子商务购物车(ASP.NET MVC / ASP.NET核心MVC),拥有庞大的社区和充满新功能的市场,主题和插件。
  • GrandNode – 基于ASP.NET Core 2.1和MongoDB的多平台免费开源电子商务购物车。
  • PayPal – 用于PayPal的RESTful API的.NET SDK。
  • SimplCommerce – 基于.NET Core构建的超级简单电子商务系统。
  • Stripe – 用于stripe.com REST API的类型.NET客户端。

异常

响应式编程

  • CSharpFunctionalExtensions – C#的功能扩展。
  • DynamicData – 基于Rx.NET的Reactive 集合。
  • echo-process – C#的Actor库,其中包含支持Redis持久性的其他模块,以及JS集成。
  • FsCheck – FsCheck是用于自动测试.NET程序的工具。
  • Giraffe – 适用于F#开发人员的本机功能ASP.NET核心Web框架。
  • language-ext – C#功能语言扩展。
  • LaYumba.Functional – C#中的函数式编程的代码示例。
  • NetMQ.ReactiveExtensions – 使用Reactive Extensions(RX)轻松地在网络上的任何位置发送消息。传输协议是ZeroMQ。
  • Optional – Optional类型库.
  • reactive-streams-dotnet – Reactive库。
  • ReactiveUI – 一个MVVM框架,它与Reactive Extensions for .NET集成,以创建在任何移动或桌面平台上运行的优雅,可测试的用户界面。
  • Rx.NET – Rx.NET库。
  • Qactive – Reactive 可查询库。
  • sodium – Reactive 多语言库。

图片

  • GLFWDotNet – GLFW的.NET绑定。
  • ImageProcessor – 一个流畅的System.Drawing包装器,用于处理图像文件。
  • ImageSharp – 图像文件处理库。
  • LibVLCSharp – LibVLCSharp是基于VideoLAN的LibVLC库的.NET平台的跨平台音频和视频API。
  • Magick.NET – 功能强大的图像处理库,支持超过100种主要文件格式(不包括子格式)。
  • MagicScaler – 适用于.NET的MagicScaler高性能,高质量图像处理管道
  • QRCoder – 二维码实现库
  • SharpBgfx – bgfx图形库的C#绑定。
  • Structure.Sketching – 用于支持.NET Core的.NET应用程序的图像处理库。
  • veldrid – 一个用于.NET的低级硬件加速3D图形库。
  • ZXing.Net 二维码、条形码的生成和读取

图形用户界面GUI

  • Avalonia – 跨平台UI框架。
  • AvaloniaEdit – 基于Avalonia的文本编辑器组件。
  • ShellProgressBar – 可视化(并行)控制台应用程序库。
  • Qml.Net – 使用Qml.Net在.NET中构建跨平台的桌面应用程序。
  • WinApi – 一个简单,直接,超薄的CLR库,用于高性能Win32 Native Interop,具有自动化,窗口,DirectX,OpenGL和Skia助手。

集成开发环境IDE

  • Mono – MonoDevelop使开发人员能够在Linux,Windows和Mac OS X上快速编写桌面和Web应用程序。
  • rider – 基于IntelliJ平台和ReSharper的跨平台C#IDE。
  • Omnisharp – 开源项目系列,每个项目都有一个目标:在您选择的编辑器中实现出色的.NET体验。
  • SharpDevelop – SharpDevelop是一个免费的集成开发环境(IDE),适用于Microsoft.NET平台上的C#,VB.NET,Boo,IronPython,IronRuby和F#项目。它(几乎)完全用C#编写,并带有您期望在IDE中使用的功能以及更多功能。
  • Visual Studio Code – 它结合了代码编辑器的简单性和开发人员的核心编辑 – 构建 – 调试周期所需的工具。VS Code提供全面的编辑和调试支持,可扩展性模型以及与现有工具的轻量级集成。
  • Visual Studio Community – 功能完备且可扩展的免费 IDE,可用于创建新式 Android、iOS、Windows 应用以及 Web 应用和云服务。

国际化

控制反转IOC

  • AutoDI – 使用IL编译的超快依赖注入库。
  • Autofac – IoC容器。
  • Castle.Windsor – IoC容器。
  • DryIoc – 快速,小巧,功能齐全的IoC。
  • Grace – Grace是一款功能丰富的依赖注入容器,其设计考虑了易用性和性能。
  • Inyector – AspNetCore的依赖注入自动化。
  • Lamar – 快速的IOC工具库。
  • LightInject – 超轻量级IoC容器。
  • SimpleInjector – 简单,灵活,快速的依赖注入库。
  • Stashbox – 基于.NET的解决方案的轻量级,可移植依赖注入框架。

日志

机器学习和科学研究

  • Accord – Accord.NET项目为.NET提供了机器学习,统计,人工智能,计算机视觉和图像处理方法。
  • ML.NET – ML.NET是.NET的开源和跨平台机器学习框架。
  • Spreads – 用于数据流实时探索和分析的库。
  • TensorFlowSharp – 适用于.NET语言的TensorFlow API。
  • WaveFunctionCollapse – 借助量子力学的思想,从单个例子​​生成itmap和tilemap。
  • SiaNet – 具有CUDA / OpenCL支持的易于使用的C#深度学习。

邮件

  • FluentEmail – 电子邮件发送库。
  • MailBody – 使用流畅的界面(.NET)创建电子邮件。
  • MailKit – 用于IMAP,POP3和SMTP的跨平台.NET库。
  • MailMergeLib – SMTP邮件客户端库,为文本,内嵌图像和附件提供邮件合并功能,以及发送邮件的良好吞吐量和容错能力。
  • MimeKit – 跨平台.NET MIME创建和解析器库,支持S/MIME, PGP, DKIM, TNEF and Unix mbox。
  • netDumbster – 用于测试的.Net假SMTP服务器。克隆流行的Dumbster。
  • Papercut – 简单桌面SMTP服务器。
  • PreMailer.Net – css和样式结合的邮件库。
  • SendGrid Client – C# library for the SendGrid v3 mail endpoint.
  • SmtpServer – 用于创建自己的SMTP服务器的库。
  • StrongGrid – SendGrid的v3 API客户端。不仅允许您发送电子邮件,还允许您批量导入联系人,管理列表和段,为列表创建自定义字段等。还包括SendGrid Webhooks的解析器。

数学

  • UnitConversion – 用于.NET Core和.NET Framework的可扩展单元转换库。
  • AutoDiff – 一个库,提供快速,准确和自动的数学函数微分(计算导数/梯度)。

大杂烩

  • AdvanceDLSupport – 基于P/Invoke的库。
  • AngleSharp – 尖括号解析器库。它解析HTML5,MathML,SVG和CSS,以构建基于官方W3C规范的DOM。可与python的beautifulsoup4相媲美。
  • AgileMapper – AgileMapper是一个零配置,高度可配置的对象 – 对象映射库,具有可查看的执行计划。
  • AspNetCore Extension Library – ASP.NET Core扩展库。
  • AutoMapper – .NET中基于约定的对象关系映射库。
  • Baget – 轻量级NuGet服务器。
  • Bleak – Windows本机DLL注入库。
  • Bullseye – 用于描述和运行目标及其依赖项的.NET包。
  • Castle.Core – Castle Core提供常见的Castle Project抽象,包括日志记录服务。
  • Chessie – Railway-oriented编程库。
  • CliWrap – 命令行界面的包装库。
  • commanddotnet – 在类中为您的命令行应用程序接口建模。
  • CommonMark.NET – 在C#中实现CommonMark规范,用于将Markdown文档转换为HTML。
  • ConsoleTableExt – 用于为.NET控制台应用程序创建表的Fluent库。
  • CoordinateSharp – 一个可以快速格式化和转换地理坐标以及提供基于位置的太阳和月亮信息(日落,日出,月亮照明等)的库。 )。
  • datatables – JQuery DataTables的帮助程序。
  • DinkToPdf – 用于wkhtmltopdf库的C#.NET包装库,它使用Webkit引擎将HTML页面转换为PDF。
  • dotnet-env – 用于从.env文件加载环境变量的.NET库。
  • DotNet.Glob – 快速通配库。优于正则表达式。
  • Dotnet outdated – 显示过时的NuGet的工具库。
  • Dotnet Script – 从.NET CLI运行C#脚本。
  • Dotnet Serve – 用于.NET Core CLI的简单命令行HTTP服务器。
  • Eighty – 一个简单的HTML生成库
  • Enums.NET – Enums.NET是一个高性能类型安全的.NET枚举实用程序库
  • FastExpressionCompiler – 快速ExpressionTree编译器。
  • FluentDocker – FluentDocker是一个与docker-machine,docker-compose和docker交互的库。
  • FluentFTP – FTP和FTPS客户端,具有广泛的FTP命令,SSL / TLS连接,散列/校验等。
  • Fody – 编辑.net程序集的可扩展工具。
  • HdrHistogram.NET – 高动态范围(HDR)直方图。
  • httpclient-interception – 用于拦截服务器端HTTP依赖关系的.NET标准库。
  • Humanizer – Humanizer满足您操作和显示字符串,枚举,日期,时间,时间跨度,数字和数量的所有.NET需求。
  • Humidifier – Humidifier允许您以编程方式构建AWS CloudFormation模板。
  • impromptu-interface – 将DLR与Reflect.Emit结合使用的库。
  • JqueryDataTablesServerSide – 用于JQuery DataTable的Asp.Net Core服务器端库,具有分页,过滤,排序和Excel导出功能。
  • LibSass Host – 围绕LibSass库的.NET包装器,能够支持虚拟文件系统。
  • markdig – 可兼容Markdown处理库。
  • NFlags – 解析CLI和开箱即用功能的库。
  • NReco.LambdaParser – 将字符串表达式(公式,方法调用,条件)解析为LINQ表达式树,可以编译为lambda并进行求值。
  • NuGet Trends – 查看NuGet软件包的采用情况以及NuGet的最新趋势程序。
  • NYoutubeDL – youtube-dl库。
  • Otp.NET – 在C#中实现TOTP RFC 6238和HOTP RFC 4226。
  • pose – 用委托替换任何.NET方法(包括静态和非虚拟)
  • PuppeteerSharp – Puppeteer Sharp是官方Node.JS Puppeteer API的.NET端口。
  • readline – 可以代替内置组件Console.ReadLine()的库。
  • ReflectionMagic – Framework to drastically simplify your private reflection code using C# dynamic
  • Relinq – 使用re-linq,现在比以往更容易创建功能齐全的LINQ提供商。
  • ReverseMarkdown – Html到Markdown转换器库,附带一些unix shell终端优势。
  • PdfReport.Core – PdfReport.Core是一个代码优先的报告引擎,它建立在iTextSharp.LGPLv2.Core和EPPlus.Core库之上。
  • Scientist – 用于重构关键路径的.NET库。它是GitHub的Ruby Scientist库的一个端口。
  • Scrutor – Microsoft.Extensions.DependencyInjection的程序集扫描扩展。
  • Sheller – 读取Shell脚本的库。
  • SmartFormat.NET – string.Format的可扩展替代品。
  • Stocks
    • Trady – Trady是一个用于计算技术指标的便捷库,它的目标是成为一个自动交易系统,提供股票数据馈送,指标计算,策略建立和自动交易。
  • System.Linq.Dynamic.Core – System Linq Dynamic功能。
  • UnitsNet – Units.NET为您提供所有常用的度量单位和它们之间的转换。
  • Validation
    • FluentValidation – 流行的.NET验证库,用于构建强类型的验证规则。
    • Guard – 高性能,可扩展的参数验证库。
    • Valit – Valit是对.NET Core的简单验证库,减少if的使用。
  • warden-stack – 针对您的应用程序,资源和基础架构的“运行状况检查”。让守望者守在手表上。
  • WebEssentials.AspNetCore.ServiceWorker – ASP.NET核心渐进式Web应用程序。
  • Xabe.FFmpeg – 用于FFmpeg的.NET标准包装器。它允许在不知道FFmpeg如何工作的情况下处理媒体,并且可以用于将自定义参数传递给来自C#应用程序的FFmpeg。
  • YoutubeExplode – 用于提取元数据和下载Youtube视频和播放列表的终极库。

网络

  • AspNetCore.Proxy – Proxy代理库。
  • CurlThin – 轻量级cURL绑定库,支持通过curl_multi接口进行多个同时传输。
  • NETStandard.HttpListener – HttpListener(NETStandard)。
  • Networker – 一个简单易用的.NET TCP和UDP网络库,旨在实现灵活,可扩展和快速。

办公软件

  • EPPlus – 使用.NET创建高级Excel电子表格。
  • npoi – 可以读取/写入未安装Microsoft Office的Office格式的.NET库。没有COM +,没有互操作。
  • Open-XML-SDK – Open XML SDK提供了使用Office Word,Excel和PowerPoint文档的工具。

操作系统

  • CosmosOS – Cosmos是操作系统的“构建工具包”。使用托管语言(例如C#,VB.NET等)构建自己的OS!

对象关系映射ORM

分析

  • Glimpse – 适用于.NET的轻量级,开源,实时诊断和洞察分析器。 不稳定的版本
  • MiniProfiler – 一个简单但有效的ASP.NET网站迷你探查器。

sql生成器

  • SqlKata – 优雅的Sql查询生成器,支持复杂查询,连接,子查询,嵌套条件,供应商引擎目标等等

消息队列

  • emitter – 连接所有设备的免费开源实时消息服务。此发布 – 订阅消息传递API是为了提高速度和安全性而构建的。
  • EventStore – 使用JavaScript中的复杂事件处理的开源,功能数据库。
  • Foundatio – 内存,redis和azure实现的通用接口。
  • MediatR – 中介模式库。
  • MediatR.Extensions.Microsoft.DependencyInjection – MediatR的扩展程序
  • Mediator.Net – .Net的简单中介,用于发送支持管道的命令,发布事件和请求响应。
  • MicroBus – MicroBus中介模式库。
  • MQTTnet – MQTTnet是一个用于基于MQTT的通信的高性能.NET库。
  • netmq – NetMQ是轻量级消息传递库。
  • OpenCQRS – 用于DDD,CQRS和事件的.NET核心库,具有Azure Service Bus集成。 Command和Event存储支持的数据库提供程序包括:DocumentDB,MongoDB,SQL Server,MySQL,PostgreSQL和SQLite。
  • rabbitmq-dotnet-client – RabbitMQ .NET客户端。
  • RawRabbit – 用于通过RabbitMq进行通信的现代.NET框架。
  • Rebus – .NET的简单和精简服务总线实现。
  • Restbus – RabbitMq的消息传递库。
  • Tossit – 简单易用的库,用于分布式作业/工作人员逻辑。内置RabbitMQ实现处理的分布式消息。

报表

  • FastReport – .NET Core 2.x / .Net Framework 4.x的开源报告生成器。 FastReport可用于MVC,Web API应用程序。

任务计划

  • Chroniton.NetCore – 用于在日程安排上运行任务(作业)的轻量级健壮库。
  • Coravel – .Net Core符合Laravel:调度,排队等
  • FluentScheduler – 具有流畅界面的自动作业调度程序。
  • Gofer.NET – 用于.NET Core的分布式后台任务/作业的简易C#API。
  • HangfireIO – 在ASP.NET应用程序内执行即发即忘,延迟和重复性工作。
  • LiquidState – 高效异步和同步状态机。
  • NCrontab – 用于.NET的Crontab。
  • quartznet – Quartz.NET任务计划程序。
  • stateless – 用于在C#代码中创建状态机的简单库。

开发工具包SDKs

  • AWS SDK – Amazon Web Services(AWS).NET Core SDK组件。每个AWS服务都有自己的NuGet包。
  • azure-event-hubs-dotnet – Azure事件中心的.NET标准客户端库。
  • Blockchain clients
  • CakeMail.RestClient – CakeMail API的客户端。允许您发送交易电子邮件,批量电子邮件,管理列表和联系人等。
  • consuldotnet – 面向领事的.NET API。
  • csharp-nats – 用于NATS消息传递系统的C#.NET客户端。
  • DarkSkyCore – .NET标准库,用于使用Dark Sky API
  • Docker.DotNet – 用于Docker API的.NET(C#)客户端库。
  • firebase-admin-dotnet – Firebase Admin .NET SDK
  • google-cloud-dotnet – 适用于.NET的Google Cloud Client Libraries。
  • Manatee.Trello – 一个完全面向对象的.Net包装器,用于Trello用C#编写的RESTful API。
  • Microphone – 使用Consul或ETCD集群的Web Api或NancyFx运行自托管REST服务的轻量级框架。
  • octokit.net – 用于.NET的GitHub API客户端库。
  • PreStorm – ArcGIS Server的并行REST客户端。
  • SendGrid-csharp – 用于使用完整SendGrid API的C#客户端库。
  • statsd-csharp-client – 与.NET标准兼容的C#客户端与Etsy的优秀服务器。
  • tweetinvi – 直观的.NET C#库,用于访问Twitter REST和STREAM API。

安全

  • aspnetcore-security-headers – 用于向ASP.NET Core应用程序添加安全标头的中间件。
  • HtmlSanitizer – 清除HTML以避免XSS攻击。
  • jose-jwt – 用于处理JOSE对象的库(JWT,JWA,JWS及相关)。
  • Jwt.Net – Jwt.Net,一个用于.NET的JWT(JSON Web令牌)实现。
  • JWT Simple Server – 用于ASP.NET Core的轻量级动态jwt服务器。
  • NWebsec – ASP.NET的安全库。
  • reCAPTCHA – 用于ASP.NET Core的reCAPTCHA 2.0。
  • roslyn-security-guard – 旨在帮助.NET应用程序进行安全审计的Roslyn分析器。
  • OwaspHeaders – .NET Core中间件,用于注入Owasp推荐的HTTP标头,以提高安全性。
  • Security – 于Web应用程序的安全性和授权的中间件。
  • SecurityHeaders – 允许向ASP.NET Core网站添加安全标头的小包。

搜索

  • Algolia.Search – 官方Algolia .NET客户端的存储库。
  • AutoComplete – 持久,简单,强大且可移植的自动完成库。
  • Elasticsearch.Net & NEST – NEST和Elasticsearch.Net的存储库,这是两个官方Elasticsearch .NET客户端。
  • ElasticsearchCRUD – Elasticsearch .NET API。
  • SearchExtensions – IQueryable接口的高级搜索功能,例如Entity Framework查询。
  • SimMetrics.Net – 相似度量标准库,例如从编辑距离(Levenshtein,Gotoh,Jaro等)到其他指标,(例如Soundex,Chapman)
  • SolrExpress – 用于Solr的简单轻量级查询.NET库,采用可控,可构建和快速失败的方式。

序列化

  • BinarySerializer – 二进制序列化库,用于控制字节和位级别的数据格式。
  • bond – 用于处理模式化数据的跨平台框架。它支持跨语言的序列化和强大的通用机制,可以有效地处理数据。 Bond广泛用于Microsoft的高规模服务。
  • Channels – 基于推送的.NET流。
  • CsvHelper – 帮助读写CSV文件的库。
  • Edi.Net – EDI Serializer / Deserializer。支持EDIFact,X12和TRADACOMS格式。
  • ExtendedXmlSerializer – 用于.NET的扩展Xml序列化程序。
  • Jil – 基于Sigil构建的快速.NET JSON(De)串行器。
  • MessagePack
  • Newtonsoft.Json – 适用于.NET的流行高性能JSON框架。
  • protobuf-net – 用于惯用.NET的协议缓冲库。
  • Schema.NET – Schema.org对象变成了强类型的C#POCO类,用于.NET。所有类都可以序列化为JSON / JSON-LD和XML,通常用于表示html页面头部的结构化数据。
  • ServiceStack.Text – JSON,JSV和CSV文本序列化器。
  • TinyCsvParser – 易于使用,易于扩展和高性能的库,用于使用.NET进行CSV解析。
  • Wire – POCO对象的二进制序列化程序。
  • YamlDotNet – .NET
  • ZeroFormatter – 用于.NET的快速二进制(de)序列化程序。
  • Utf8Json – 用于C#(.NET,.NET Core,Unity,Xamarin)的绝对最快和零分配JSON序列化器。
  • YAXLib – 用于.NET Framework和.NET Core的XML序列化库。非常灵活和强大。

模板引擎

  • dotliquid – TobiasLütke的Liquid模板语言的.NET端口。
  • fluid – 开源.NET模板引擎,尽可能接近Liquid模板语言。
  • Portable.Xaml – 用于读/写xaml文件的可移植.NET库。
  • Razor – 用于MVC Web应用程序视图页面的CSHTML文件的分析器和代码生成器。
  • RazorLight – 基于Microsoft针对.NET Core的Razor解析引擎的模板引擎。
  • Scriban – A fast, powerful, safe and lightweight text templating language and engine for .NET.

测试

  • Bogus – 简单而健全的C#假数据生成器。基于并从着名的faker.js移植。
  • CoreBDD – xUnit.net的BDD框架
  • FakeItEasy – .NET的简易模拟库。
  • FluentAssertions – 一组.NET扩展方法,允许您更自然地指定TDD或BDD样式测试的预期结果。
  • GenFu – 可用于生成实际测试数据的库。
  • LightBDD – BDD框架允许创建易于阅读和维护的测试。
  • mockhttp – 为Microsoft的HttpClient库测试图层。
  • moq.netcore – 最受欢迎且最友好的.NET模拟框架。
  • MSpec – 用于编写BDD样式测试的流行测试框架。
  • MyTested.AspNetCore.Mvc – 流畅的测试 framework for ASP.NET Core MVC.
  • Netling – 加载测试客户端,以便轻松进行Web测试。
  • NSpec – 针对C#的战斗强化测试框架,受Mocha和RSpec的启发。
  • NSubstitute – .NET模拟框架的友好替代品。
  • nunit – 面向.NET Core的NUnit测试运行器。
  • shouldly – 断言框架Should be!
  • SpecFlow – SpecFlow是用于.NET的实用BDD解决方案。
  • Storyteller – 一种制定可执行规范的工具。
  • Stubbery – 一个用于在.NET中创建和运行Api存根的简单库。
  • Testavior – Testavior是一个轻量级解决方案,可帮助您开发ASP.NET Core的行为测试。
  • TestStack.BDDfy – 最简单的BDD框架!
  • xBehave.net – 一个xUnit.net扩展,用于描述使用自然语言的测试。
  • xUnit.net – 一个免费的,开源的,以社区为中心的.NET Framework单元测试工具。

工具

  • CommandLineUtils – .NET Core和.NET Framework的命令行解析和实用程序。
  • docfx – 用于构建和发布.NET项目API文档的工具
  • dotnetfiddle – .NET沙箱,供开发人员快速尝试代码和共享代码片段。
  • dotnet-tools – .NET Core命令行(dotnet CLI)的工具扩展列表。
  • EntryPoint – .Net Core和.Net Framework 4.5+的可组合CLI(命令行)参数解析器。
  • Fake JSON Server – 用于原型设计或作为CRUD后端的假REST API。无需定义类型,使用动态类型。数据存储在单个JSON文件中。具有身份验证,WebSocket通知,异步长时间运行操作,错误/延迟的随机生成以及实验性GraphQL支持。
  • gitignore.io – 为您的项目创建有用的.gitignore文件。
  • ICanHasDotnetCore – 扫描上传的packages.config文件或GitHub存储库,并确定nuget包是否针对.NET Standard。
  • json2csharp – 从JSON生成C#类。
  • letsencrypt-win-simple – 适用于Windows的简单ACME客户端。
  • Linq_Faster – 数组,Span 和List 的类似于Linq的扩展。
  • mRemoteNG – 下一代mRemote,开源,标签,多协议,远程连接管理器
  • NJsonSchema – NJsonSchema是一个.NET库,用于读取,生成和验证JSON Schema draft v4 + schemas。
  • NuKeeper – 自动更新.NET项目中的nuget包。
  • NuGetPackageExplorer – 使用GUI创建,更新和部署Nuget软件包。
  • NugetVisualizer – 为一组给定的git存储库或文件夹可视化所有nuget包及其相应的版本。
  • OctoLinker – 使用适用于GitHub的OctoLinker浏览器扩展,有效地浏览projects.json文件。
  • posh-dotnet – [dotnet CLI]的“PowerShell”标签完成(https://github.com/dotnet/cli)。
  • Rin – ASP.NET Core的请求/响应Inspector中间件。像Glimpse。
  • scoop – Windows的命令行安装程序。
  • SerilogAnalyzer – 使用Serilog日志库对基于Roslyn的代码进行分析。检查常见错误和使用问题。
  • SharpZipLib – #ziplib是一个完全用C#编写的适用于.NET平台的Zip,GZip,Tar和BZip2库。
  • ShareX – 免费的开源程序,可让您捕捉或记录屏幕的任何区域,只需按一下键即可共享。它还允许将图像,文本或其他类型的文件上传到80多个支持的目的地,您可以从中选择。 https://getsharex.com
  • SharpLab – .NET代码游乐场,显示代码编译的中间步骤和结果。 https://sharplab.io
  • sourcelink – SourceLink是一个语言和源代码控制不可知系统,用于为二进制文件提供一流的源代码调试体验。
  • System.CommandLine – System.CommandLine:命令行解析,调用和呈现终端输出。
  • X.Web.Sitemap – 简单站点地图生成器。
  • X.Web.RSS – 简单站点RSS生成器。
  • SmartCode – SmartCode= IDataSource -> IBuildTask -> IOutput => Build Everything!!! (Including [Code generator])

Web框架

  • WebAssembly
    • Blazor – Blazor是使用C#/ Razor和HTML的.NET Web框架,可在带有WebAssembly的浏览器中运行。
      • Awesome Blazor – Blazor的资源,Blazor是使用C#/ Razor和HTML的.NET Web框架,可在具有WebAssembly的浏览器中运行。
      • Blazor Redux – 将Redux状态存储与Blazor连接。
    • Ooui – 是使用Web技术的.NET跨平台的小型UI库。
  • ReactJS.NET – 用于JSX编译和React组件的服务器端呈现的.NET库。
  • redux.NET – .NET应用程序的可预测状态容器。

Web Socket

  • Fleck – Fleck是C#中的WebSocket服务器实现。 Fleck不需要继承,容器或其他引用。
  • SignalR Server – Web应用程序的实时Web功能,包括服务器端推送。
  • SuperSocket – 轻量级,跨平台和可扩展的套接字服务器应用程序框架。
  • WampSharp – [Web应用程序消息传递协议]的C#实现- 提供远程消息传递模式的协议过程通过WebSockets调用和发布/预订。
  • websocket-manager – ASP .NET Core的实时库。

Windows服务

工作流

  • CoreWF – Windows Workflow Foundation(WF)到.NET Core的端口。
  • workflow-core – .NET Standard的轻量级工作流引擎。
  • WorkflowEngine.NET – 在应用程序中添加工作流程的组件。
  • Wexflow – 高性能,可扩展,模块化和跨平台的工作流引擎。

线路图

入门套件

  • Arch – .NET Core库的集合。
    • AutoHistory – 自动记录数据更改历史记录的插件。
  • AspNetCore-Angular2-Universal – 跨平台 – 用于SEO,Bootstrap,i18n国际化(ngx-translate),Webpack的服务器端渲染,TypeScript,带Karma的单元测试,WebAPI REST设置,SignalR,Swagger文档等等!
  • ASP.NET Core Starter Kit – 使用Visual Studio Code,C#,F#,JavaScript,ASP.NET Core,EF Core,React(ReactJS),Redux,Babel进行跨平台的Web开发。单页应用样板。
  • aspnetcore-spa generator – Yeoman生成器,用于构建全新的ASP.NET Core单页面应用程序,该应用程序使用Angular 2 / React / React与Redux / Knockout / Aurelia在客户端上。
  • ASP.Net Core Vue Starter – Asp.NETCore 2.0 Vue 2(ES6)SPA入门套件,包含路由,Vuex等等!
  • bitwarden-core – 核心基础设施后端(API,数据库等)https://bitwarden.com
  • dotNetify – 构建实时HTML5 / C#.NET Web应用程序的简单,轻量级但功能强大的方法。
  • generator-aspnet – 用于ASP.NET Core的yo生成器。
  • Nucleus – 在后端使用ASP.NET Core API分层架构和基于JWT的身份验证的Vue启动应用程序模板
  • react-aspnet-boilerplate – 使用ASP.NET Core 1构建同构React应用程序的起点,利用现有技术。
  • saaskit – 用于构建SaaS应用程序的开发人员工具包。
  • serverlessDotNetStarter – .NET Core入门解决方案-通过无服务器框架进行部署,并且可以在VS Code中进行本地调试。

例子

文章

书籍

备忘录

视频学习

视频播客

C#净化版WebApi框架 - kiba518 - 博客园

mikel阅读(781)

来源: C#净化版WebApi框架 – kiba518 – 博客园

前言

我们都知道WebApi是依赖于ASP.NET MVC的HttpRouteCollection进行路由 。

但WebApi和MVC之间是没有依赖关系的, WebApi的基类ApiController继承于IHttpController,而MVC的基类Controller 继承于IController。

WebApi和MVC虽然都使用HttpRouteCollection进行路由,但WebApi经历的通道是ServicesContainer,而MVC经历通道还是原始的IHttpModule。

但用Visual Studio创建的MVC WebApi项目通常会带很多功能,而这些功能,很多是我们并不想用的,或者我们想用其他开源控件代替它。

而直接创建空项目的WebApi又太原始。

所以,让我们编写一个简洁版本的WebApi吧。

净化版WebApi预览

首先,我们先看下净化版WebApi的结构。

如上图所示,代码结构很简单,除开配置文件,整个Web项目只有2个文件;而需要被调用的WebApi都被封装到了WebApi程序集中了。

接下来我们一起看下编写这个净化版WebApi的过程吧。

净化版WebApi编写

WebApiConfig

首先,引入必要的Dll,如下图所示。

然后,我们编写Web项目的写WebApiConfig;代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new WebApiAttribute());
        // 解决json序列化时的循环引用问题
        config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        // 对 JSON 数据使用混合大小写。跟属性名同样的大小.输出
        config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver();
        // Web API 路由
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name"DefaultApi",
            routeTemplate: "webapi/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

可以看到WebApiConfig是个静态类中,我们在其中创建了静态注册方法Register,在方法内,我们主要在做一件事,那就是为HttpConfiguration对象做配置。

而在配置中,我们将WepApi的路由配置成了webapi/{controller}/{id},也就是说,我们的WebApi未来的访问地址将为【http://localhost:5180/webapi/Login】这样的模式。

在WebApiConfig类中,我们还用到了这样一个类WebApiAttribute,我们在为HttpConfiguration对象的Filters属性,添加了这个类的对象。

通过Filters属性这个字样,我们可以得出,这个类主要应用应该是过滤。

下面我们看一下这个类的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class WebApiAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        //API执行前触发
        if (true)//当前设置,所有API都可以被调用
        {
            base.OnActionExecuting(actionContext);
        }
        else
        {
            throw new Exception("Error");
        }
    
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        //API执行后触发 若发生例外则不在这边处理
        if (actionExecutedContext.Exception != null)
            return;
        base.OnActionExecuted(actionExecutedContext);
    }
}

通过阅读代码,我们应该可以发现,这是一个AOP的过滤器。

在执行真正WebApi之前,会先进入这里进行过滤,过滤通过的API,才会调用 base.OnActionExecuting(actionContext)方法进行调用和执行。

结束调用同理,结束调用前,会在该类中进行拦截和过滤处理。

配置文件

WebApiConfig编写结束了,现在,我们需要将这个静态类注册到项目中。

打开Global.asax文件,编写如下代码:

1
2
3
4
5
protected void Application_Start()
{
    GlobalConfiguration.Configure(WebApiConfig.Register); 
    GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();//删除XML格式 回應
}

可以看到,我们已通过Configure方法,将我们编写好的WebApiConfig添加到了全局配置中了。

因为网站访问都存在跨域问题,所以我们再向Global.asax中添加如下代码处理:

1
2
3
4
5
6
7
8
9
10
protected void Application_BeginRequest(object sender, System.EventArgs e)
{
    var req = System.Web.HttpContext.Current.Request;
    if (req.HttpMethod == "OPTIONS")//过滤options请求,用于js跨域
    {
        Response.StatusCode = 200;
        Response.SubStatusCode = 200;
        Response.End();
    }
}

到此Web项目的编写就完成了,下面我们在WebApi程序集中,编写个简单的WebApi,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LoginController : BaseApiController
{
    public BaseResult Get()
    {
        try
        
            return new BaseResult() { IsSuccess=true };
        }
        catch (Exception ex)
        {
            
            throw ex;
        }
    }<br>}
public class BaseApiController : ApiController
{  
    public string Options()
    {
        return null;
    }
}

然后我们运行网站,进行WebApi访问。

如上图所示,我们的WebApi访问成功。

—————————————————————————————————-

到此C#净化版WebApi框架就介绍完了。

框架代码已经传到Github上了,欢迎大家下载。

Github地址:https://github.com/kiba518/WebApi

—————————————————————————————————-

JWT+ASP.NET Core集成方案 - 、天上月 - 博客园

mikel阅读(689)

来源: JWT+ASP.NET Core集成方案 – 、天上月 – 博客园

JWT

JSON Web Token 经过数字签名后,无法伪造,一个能够在各方之间安全的传输JSON对象的开放标准(RFC 7519

创建项目和解决方案

dotnet new webapi -n SampleApi
cd SampleApi
dotnet new sln -n SampleApp
dotnet sln  add .\SampleApi.csproj

引用包

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

该包已经依赖Microsoft.IdentityModel.TokensSystem.IdentityModel.Tokens.Jwt,该包由Azure AD 团队提供,所以不在aspnetcore6 运行时中。

  • 或直接修改jwtaspnetcore.csproj,引用包
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
  • appsettings.json

  "Authentication": {
    "JwtBearer": {
      "Issuer": "http://api.sampleapi.com",
      "Audience": "SampleApi",
      "SecurityKey": "SecurityKey23456"
    }
  }
  • Issuer:令牌的颁发者。一般就写成域名,实际可任意
  • Audience 颁发给谁。一般写成项目名,实际可任意
  • SecurityKey:签名验证的KEY;至少 128bit ,即16个英文字符以上,实际可任意英文字符

定义一个JwtSettings


public class JwtSettings
{
    public JwtSettings(byte[] key, string issuer, string audience)
    {
        Key = key;
        Issuer = issuer;
        Audience = audience;
    }

    /// <summary>
    ///令牌的颁发者
    /// </summary>
    public string Issuer { get; }

    /// <summary>
    /// 颁发给谁
    /// </summary>
    public string Audience { get; }

    public byte[] Key { get; }

    public TokenValidationParameters TokenValidationParameters => new TokenValidationParameters
    {
        //验证Issuer和Audience
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
        ValidateLifetime = true,
        ValidIssuer = Issuer,
        ValidAudience = Audience,
        IssuerSigningKey = new SymmetricSecurityKey(Key)
    };

    public static JwtSettings FromConfiguration(IConfiguration configuration)
    {
        var issuser = configuration["Authentication:JwtBearer:Issuer"] ?? "default_issuer";
        var auidence = configuration["Authentication:JwtBearer:Audience"] ?? "default_auidence";
        var securityKey = configuration["Authentication:JwtBearer:SecurityKey"] ?? "default_securitykey";

        byte[] key = Encoding.ASCII.GetBytes(securityKey);

        return new JwtSettings(key, issuser, auidence);
    }
}

中间件Middleware引用

        app.UseAuthentication();//认证
        app.UseAuthorization();//授权

定义JWT扩展方法服务注入

    public static IServiceCollection AddJwt(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddScoped<IStorageUserService, StorageUserService>();

        var jwtSettings = JwtSettings.FromConfiguration(configuration);
        services.AddSingleton(jwtSettings);

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options => options.TokenValidationParameters = jwtSettings.TokenValidationParameters);

        return services;
    }

引用服务

services.AddJwt(Configuration);

定义一个数据库的实体类,数据库访问 为模拟数据

public class SysUser
{
    public int Id { get; set; }
    public string UserName { get; set; }
}
public interface IStorageUserService
{
    /// <summary>
    /// 根据登录验证用户
    /// </summary>
    /// <param name="loginInfo"></param>
    /// <returns></returns>
    Task<SysUser> CheckPasswordAsync(LoginInfo loginInfo);
}
public class StorageUserService : IStorageUserService
{
    public async Task<SysUser> CheckPasswordAsync(LoginInfo loginInfo)
    {
        return await Task.FromResult(
          new SysUser 
          { 
            Id = new Random().Next(10000), 
            UserName = loginInfo.UserName 
          }
        );
    }
}

AuthController登录GenerateToken

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using SampleApi.Models;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace SampleApi.Auth;

/// <summary>
/// 登录认证个人信息
/// </summary>
[ApiController]
[Route("/api/[controller]/[action]")]
[AllowAnonymous]
public class AuthController : ControllerBase
{
    private readonly IStorageUserService _userService;
    private readonly JwtSettings _jwtSettings;

    public AuthController(JwtSettings jwtSettings, IStorageUserService userService)
    {
        _jwtSettings = jwtSettings;
        _userService = userService;
    }

    /// <summary>
    /// 登录,生成访问Toekn
    /// </summary>
    /// <param name="loginInfo"></param>
    /// <returns></returns>
    [HttpPost]
    public async Task<IActionResult> GenerateToken(LoginInfo loginInfo)
    {
        SysUser user = await _userService.CheckPasswordAsync(loginInfo);
        if (user == null)
        {
            return Ok(new
            {
                Status = false,
                Message = "账号或密码错误"
            });
        }

        var claims = new List<Claim>();

        claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
        claims.Add(new Claim(ClaimTypes.Name, user.UserName));

        var key = new SymmetricSecurityKey(_jwtSettings.Key);
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _jwtSettings.Issuer,
            audience: _jwtSettings.Audience,
            claims: claims,
            expires: DateTime.Now.AddMinutes(120),
            signingCredentials: creds
            );
        return Ok(new
        {
            Status = true,
            Token = new JwtSecurityTokenHandler().WriteToken(token)
        });
    }
}

aspnetcore6默认集成了swagger,直接运行项目,实际上为模拟数据库请求,所以点击登录接口即可。

{
  "status": true,
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijc4NjciLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNjQzMDMyNzA1LCJpc3MiOiJodHRwOi8vYXBpLnNhbXBsZWFwaS5jb20iLCJhdWQiOiJTYW1wbGVBcGkifQ.Rl8XAt2u0aZRxEJw2mVUnV6S9WzQ65qUYjqXDTneCxE"
}

当使用Swagger测试时,增加,可配置全局请求头。增加一个扩展方法。

services.AddSwagger(Configuration);
  public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddSwaggerGen(options =>
        {
            try
            {
                options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{typeof(Startup).Assembly.GetName().Name}.xml"), true);
            }
            catch (Exception ex)
            {
                Log.Warning(ex.Message);
            }
            options.SwaggerDoc("v1", new OpenApiInfo
            {
                Title = "SampleApp - HTTP API",
                Version = "v1",
                Description = "The SampleApp Microservice HTTP API. This is a Data-Driven/CRUD microservice sample"
            });

            options.AddSecurityRequirement(new OpenApiSecurityRequirement()
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference()
                            {
                                Id =  "Bearer",
                                Type = ReferenceType.SecurityScheme
                            }
                        },
                        Array.Empty<string>()
                    }
                });
            options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme
            {
                Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
                Name = "Authorization", //jwt默认的参数名称
                In = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中)
                Type = SecuritySchemeType.ApiKey
            });

        });
        services.AddEndpointsApiExplorer();

        return services;

    }

获取当前用户信息

    /// <summary>
    /// 编码Token
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpGet]
    [AllowAnonymous]
    public CurrentUser DecodeToken(string token)
    {
        var jwtTokenHandler = new JwtSecurityTokenHandler();

        if (jwtTokenHandler.CanReadToken(token))
        {
            JwtPayload jwtPayload = new JwtSecurityTokenHandler().ReadJwtToken(token).Payload;
            string? userIdOrNull = jwtPayload.Claims.FirstOrDefault(r => r.Type == ClaimTypes.NameIdentifier)?.Value;
            string? UserName = jwtPayload.Claims.FirstOrDefault(r => r.Type == ClaimTypes.Name)?.Value;
            CurrentUser currentUser = new CurrentUser
            {
                UserId = userIdOrNull == null ? null : Convert.ToInt32(userIdOrNull),
                UserName = UserName
            };
            return currentUser;
        }
        return null;
    }

根据请求头获取用户信息

IStorageUserService增加接口,StorageUserService的实现,创建一个CurrentUser类

public class StorageUserService : IStorageUserService
{
    private readonly IHttpContextAccessor _contextAccessor;

    public StorageUserService(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    public async Task<CurrentUser> GetUserByRequestContext()
    {
        var user = _contextAccessor.HttpContext.User;

        string? userIdOrNull = user.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
        string? UserName = user.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;

        CurrentUser currentUser = new CurrentUser
        {
            IsAuthenticated = user.Identity.IsAuthenticated,
            UserId = userIdOrNull == null ? null : Convert.ToInt32(userIdOrNull),
            UserName = UserName
        };
        return await Task.FromResult(currentUser);
    }
}

public class CurrentUser
{
    /// <summary>
    /// 是否登录
    /// </summary>
    public bool IsAuthenticated { get; set; }
    /// <summary>
    /// 用户Id
    /// </summary>
    public int? UserId { get; set; }
    /// <summary>
    /// 用户名
    /// </summary>
    public string? UserName { get; set; }
}
public interface IStorageUserService
{
    /// <summary>
    /// 根据Request Header携带Authorization:Bearer+空格+AccessToken获取当前登录人信息
    /// </summary>
    /// <returns></returns>
    Task<CurrentUser> GetUserByRequestContext();
}

AuthController调用服务

    /// <summary>
    /// 根据Request Header携带Authorization:Bearer+空格+AccessToken获取当前登录人信息
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    [Authorize]
    public async Task<CurrentUser> GetUserByRequestContext()
    {
        return await _userService.GetUserByRequestContext();
    }

在swagger右上角,点击Authorize,header的参数结构: “Authorization: Bearer+空格+ {token}”

开源地址

SampleApp/SampleApi at master · luoyunchong/SampleApp (github.com)

.NET +JWT

JSON Web Token Libraries – jwt.io 可以看到,.NET有6个类库实现了JWT。

有二个常用的。

  1. 微软 Azure团队的实现:AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet: IdentityModel extensions for .Net (github.com)
  2. jwt-dotnet/jwt: Jwt.Net, a JWT (JSON Web Token) implementation for .NET (github.com)

C# 理解lock - 闯.漠北 - 博客园

mikel阅读(644)

来源: C# 理解lock – 闯.漠北 – 博客园

一. 为什么要lock,lock了什么?

当我们使用线程的时候,效率最高的方式当然是异步,即各个线程同时运行,其间不相互依赖和等待。但当不同的线程都需要访问某个资源的时候,就需要同步机制了,也就是说当对同一个资源进行读写的时候,我们要使该资源在同一时刻只能被一个线程操作,以确保每个操作都是有效即时的,也即保证其操作的原子性。lock是C#中最常用的同步方式,格式为lock(objectA){codeB} 。

lock(objectA){codeB} 看似简单,实际上有三个意思,这对于适当地使用它至关重要:
1. objectA被lock了吗?没有则由我来lock,否则一直等待,直至objectA被释放。
2. lock以后在执行codeB的期间其他线程不能调用codeB,也不能使用objectA。
3. 执行完codeB之后释放objectA,并且codeB可以被其他线程访问。

二. lock(this)怎么了?

我们看一个例子:

  1. using System;
  2. using System.Threading;
  3. namespace Namespace1
  4. {
  5.     class C1
  6.     {
  7.         private bool deadlocked = true;
  8.         //这个方法用到了lock,我们希望lock的代码在同一时刻只能由一个线程访问
  9.         public void LockMe(object o)
  10.         {
  11.             lock (this)
  12.             {
  13.                 while(deadlocked)
  14.                 {
  15.                     deadlocked = (bool)o;
  16.                     Console.WriteLine(“Foo: I am locked :(“);
  17.                     Thread.Sleep(500);
  18.                 }
  19.             }
  20.         }
  21.         //所有线程都可以同时访问的方法
  22.         public void DoNotLockMe()
  23.         {
  24.             Console.WriteLine(“I am not locked :)”);
  25.         }
  26.     }
  27.     class Program
  28.     {
  29.         static void Main(string[] args)
  30.         {
  31.             C1 c1 = new C1();
  32.             //在t1线程中调用LockMe,并将deadlock设为true(将出现死锁)
  33.             Thread t1 = new Thread(c1.LockMe);
  34.             t1.Start(true);
  35.             Thread.Sleep(100);
  36.             //在主线程中lock c1
  37.             lock (c1)
  38.             {
  39.                 //调用没有被lock的方法
  40.                 c1.DoNotLockMe();
  41.                 //调用被lock的方法,并试图将deadlock解除
  42.                 c1.LockMe(false);
  43.             }
  44.         }
  45.     }

复制代码

在t1线程中,LockMe调用了lock(this), 也就是Main函数中的c1,这时候在主线程中调用lock(c1)时,必须要等待t1中的lock块执行完毕之后才能访问c1,即所有c1相关的操作都无法完成,于是我们看到连c1.DoNotLockMe()都没有执行。

把C1的代码稍作改动:

  1.     class C1
  2.     {
  3.         private bool deadlocked = true;
  4.         private object locker = new object();
  5.         //这个方法用到了lock,我们希望lock的代码在同一时刻只能由一个线程访问
  6.         public void LockMe(object o)
  7.         {
  8.             lock (locker)
  9.             {
  10.                 while(deadlocked)
  11.                 {
  12.                     deadlocked = (bool)o;
  13.                     Console.WriteLine(“Foo: I am locked :(“);
  14.                     Thread.Sleep(500);
  15.                 }
  16.             }
  17.         }
  18.         //所有线程都可以同时访问的方法
  19.         public void DoNotLockMe()
  20.         {
  21.             Console.WriteLine(“I am not locked :)”);
  22.         }
  23.     }

复制代码

这次我们使用一个私有成员作为锁定变量(locker),在LockMe中仅仅锁定这个私有locker,而不是整个对象。这时候重新运行程序,可以看到虽然t1出现了死锁,DoNotLockMe()仍然可以由主线程访问;LockMe()依然不能访问,原因是其中锁定的locker还没有被t1释放。

关键点:
1. lock(this)的缺点就是在一个线程(例如本例的t1)通过执行该类的某个使用”lock(this)”的方法(例如本例的LockMe())锁定某对象之后, 导致整个对象无法被其他线程(例如本例的主线程)访问 – 因为很多人在其他线程(例如本例的主线程)中使用该类的时候会使用类似lock(c1)的代码。
2. 锁定的不仅仅是lock段里的代码,锁本身也是线程安全的。
3. 我们应该使用不影响其他操作的私有对象作为locker。
4. 在使用lock的时候,被lock的对象(locker)一定要是引用类型的,如果是值类型,将导致每次lock的时候都会将该对象装箱为一个新的引用对象(事实上如果使用值类型,C#编译器(3.5.30729.1)在编译时就会给出一个错误)。

第二节:抢单流程优化1(小白写法→lock写法→服务器缓存+队列(含lock)→Redis缓存+原子性+队列【干掉lock】) - Yaopengfei - 博客园

mikel阅读(568)

来源: 第二节:抢单流程优化1(小白写法→lock写法→服务器缓存+队列(含lock)→Redis缓存+原子性+队列【干掉lock】) – Yaopengfei – 博客园

一. 小白写法

1.设计思路

纯DB操作

DB查库存→判断库存→(DB扣减库存+DB创建订单)

2.分析

A.响应非常慢,导致大量请求拿不到结果而报错

B.存在超卖现象

C.扣减库存错误

3.压测结果

前提:原库存为10000,这里统计2s内可处理的并发数,以90%百分位为例,要求错误率为0。

代码分享:

复制代码
       /// <summary>
        /// 原始版本-纯DB操作
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <param name="arcId">商品编号</param>
        /// <param name="totalPrice">订单总额</param>
        /// <returns></returns>
        public string POrder1(string userId, string arcId, string totalPrice)
        {
            try
            {
                //1. 查询库存
                var sArctile = _baseService.Entities<T_SeckillArticle>().Where(u => u.articleId == arcId).FirstOrDefault();
                if (sArctile.articleStockNum - 1 > 0)
                {
                    //2. 扣减库存
                    sArctile.articleStockNum--;

                    //3. 进行下单
                    T_Order tOrder = new T_Order();
                    tOrder.id = Guid.NewGuid().ToString("N");
                    tOrder.userId = userId;
                    tOrder.orderNum = Guid.NewGuid().ToString("N");
                    tOrder.articleId = arcId;
                    tOrder.orderTotalPrice = Convert.ToDecimal(totalPrice);
                    tOrder.addTime = DateTime.Now;
                    tOrder.orderStatus = 0;
                    _baseService.Add<T_Order>(tOrder);
                    _baseService.SaveChange();

                    return "下单成功";
                }
                else
                {
                    //卖完了
                    return "卖完了";
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }

        }
复制代码

测试结果:

(1). 100并发,需要1788ms,订单数量插入正确,但库存扣减错误。

(2). 200并发,需要4453ms,订单数量插入正确,但库存扣减错误。

 

二. lock写法

1.设计思路

纯DB操作的基础上Lock锁

Lock { DB查库存→判断库存→(DB扣减库存+DB创建订单) }

2.分析

A. 解决超卖现象

B. 响应依旧非常慢,导致大量请求拿到结果而报错

3.压测结果

前提:原库存为10000,这里统计2s内可处理的并发数,以90%百分位为例,要求错误率为0。

代码分享:

复制代码
        /// <summary>
        /// 02-纯DB操作+Lock锁
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <param name="arcId">商品编号</param>
        /// <param name="totalPrice">订单总额</param>
        /// <returns></returns>
        public string POrder2(string userId, string arcId, string totalPrice)
        {
            try
            {
                lock (_lock)
                {
                    //1. 查询库存
                    var sArctile = _baseService.Entities<T_SeckillArticle>().Where(u => u.articleId == arcId).FirstOrDefault();
                    if (sArctile.articleStockNum - 1 > 0)
                    {
                        //2. 扣减库存
                        sArctile.articleStockNum--;

                        //3. 进行下单
                        T_Order tOrder = new T_Order();
                        tOrder.id = Guid.NewGuid().ToString("N");
                        tOrder.userId = userId;
                        tOrder.orderNum = Guid.NewGuid().ToString("N");
                        tOrder.articleId = arcId;
                        tOrder.orderTotalPrice = Convert.ToDecimal(totalPrice);
                        tOrder.addTime = DateTime.Now;
                        tOrder.orderStatus = 0;
                        _baseService.Add<T_Order>(tOrder);
                        _baseService.SaveChange();

                        return "下单成功";
                    }
                    else
                    {
                        //卖完了
                        return "卖完了";
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
复制代码

(1). 30并发,需要2132ms,订单数量插入正确,库存扣减正确。

(2). 100并发,需要9186ms,订单数量插入正确,库存扣减正确。

 

三. 服务器缓存+队列

1.设计思路

生产者和消费者模式→流量削峰(异步的模式平滑处理请求)

A. Lock{ 事先同步DB库存到缓存→缓存查库存→判断库存→订单相关信息服务端队列中 }

B. 消费者从队列中取数据批量提交信息,依次进行(DB扣减库存+DB创建订单)

2.分析

A. 接口中彻底干掉了DB操作, 并发数提升非常大

B. 服务宕机,原队列中的下单信息全部丢失

C. 但是生产者和消费者必须在一个项目及一个进程内

3.压测结果

前提:原库存为10000,这里统计2s内可处理的并发数,以90%百分位为例,要求错误率为0。

代码分享:

初始化库存到内存缓存中

复制代码
    /// <summary>
    /// 后台任务-初始化库存到缓存中
    /// </summary>
    public class CacheBackService : BackgroundService
    {
        private IMemoryCache _cache;
        private StackExchange.Redis.IDatabase _redisDb;
        private IConfiguration _Configuration;

        public CacheBackService(IMemoryCache cache,RedisHelp redisHelp, IConfiguration Configuration)
        {
            _cache = cache;
            _redisDb = redisHelp.GetDatabase();
            _Configuration = Configuration;
        }

        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // EFCore的上下文默认注入的请求内单例的,而CacheBackService要注册成全局单例的
            // 由于二者的生命周期不同,所以不能相互注入调用,这里手动new一个EF上下文
            var optionsBuilder = new DbContextOptionsBuilder<ESHOPContext>();
            optionsBuilder.UseSqlServer(_Configuration.GetConnectionString("EFStr"));
            ESHOPContext context = new ESHOPContext(optionsBuilder.Options);
            IBaseService _baseService = new BaseService(context);

            //初始化库存信息,连临时写在这个位置,充当服务器启动的时候初始化
            var data = await _baseService.Entities<T_SeckillArticle>().Where(u => u.id == "300001").FirstOrDefaultAsync();
   //服务器缓存
                _cache.Set<int>($"{data.articleId}-sCount", data.articleStockNum);      
        }
    }
复制代码

队列定义和下单接口

复制代码
    /// <summary>
    /// 基于内存的队列
    /// </summary>
    public static class MyQueue
    {
      private static  ConcurrentQueue<string> _queue = new ConcurrentQueue<string>();
        public static ConcurrentQueue<string> GetQueue()
        {
            return _queue;
        }
    }
        /// <summary>
        /// 03-服务端缓存+队列版本+Lock
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <param name="arcId">商品编号</param>
        /// <param name="totalPrice">订单总额</param>
        /// <returns></returns>
        public string POrder3(string userId, string arcId, string totalPrice)
        {
            try
            {
                lock (_lock)
                {
                    //1. 查询库存
                    int count = _cache.Get<int>($"{arcId}-sCount");
                    if (count - 1 >= 0)
                    {
                        //2. 扣减库存
                        count = count - 1;
                        _cache.Set<int>($"{arcId}-sCount", count);

                        //3. 将下单信息存到消息队列中
                        var orderNum = Guid.NewGuid().ToString("N");
                        MyQueue.GetQueue().Enqueue($"{userId}-{arcId}-{totalPrice}-{orderNum}");

                        //4. 把部分订单信息返回给前端
                        return $"下单成功,订单信息为:userId={userId},arcId={arcId},orderNum={orderNum}";
                    }
                    else
                    {
                        //卖完了
                        return "卖完了";
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
复制代码

基于内存的消费者

复制代码
     /// <summary>
    /// 后台任务--基于内存队列的消费者(已经测试)
    /// </summary>
    public class CustomerService : BackgroundService
    {
        private IConfiguration _Configuration;
        public CustomerService(IConfiguration Configuration)
        {
            _Configuration = Configuration;
        }
        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
        {

            // EFCore的上下文默认注入的请求内单例的,而CacheBackService要注册成全局单例的
            // 由于二者的生命周期不同,所以不能相互注入调用,这里手动new一个EF上下文
            var optionsBuilder = new DbContextOptionsBuilder<ESHOPContext>();
            optionsBuilder.UseSqlServer(_Configuration.GetConnectionString("EFStr"));
            ESHOPContext context = new ESHOPContext(optionsBuilder.Options);
            IBaseService _baseService = new BaseService(context);

            Console.WriteLine("下面开始执行消费业务");
            while (true)
            {
                try
                {
                    string data = "";
                    MyQueue.GetQueue().TryDequeue(out data);
                    if (!string.IsNullOrEmpty(data))
                    {
                        List<string> tempData = data.Split('-').ToList();
                        //1.扣减库存---禁止状态追踪
                        var sArctile = context.Set<T_SeckillArticle>().AsNoTracking().Where(u => u.id == "300001").FirstOrDefault();
                        sArctile.articleStockNum = sArctile.articleStockNum - 1;
                        context.Update(sArctile);

                        //2. 插入订单信息
                        T_Order tOrder = new T_Order();
                        tOrder.id = Guid.NewGuid().ToString("N");
                        tOrder.userId = tempData[0];
                        tOrder.orderNum = tempData[3];
                        tOrder.articleId = tempData[1];
                        tOrder.orderTotalPrice = Convert.ToDecimal(tempData[2]);
                        tOrder.addTime = DateTime.Now;
                        tOrder.orderStatus = 0;
                        context.Add<T_Order>(tOrder);
                        int count = await context.SaveChangesAsync();

                        //释放一下
                        context.Entry<T_SeckillArticle>(sArctile).State = EntityState.Detached;
                        Console.WriteLine($"执行成功,条数为:{count},当前库存为:{ sArctile.articleStockNum}");
                    }
                    else
                    {
                        Console.WriteLine("暂时没有订单信息,休息一下");
                        await Task.Delay(TimeSpan.FromSeconds(1));
                    }
                }
                catch (Exception ex)
                {

                    Console.WriteLine($"执行失败:{ex.Message}");
                }
            }
        }
    }
复制代码

(1). 1000并发,需要600ms,订单数量插入正确,库存扣减正确。

(2). 2000并发,需要1500ms,订单数量插入正确,库存扣减正确。

 

 

四. Redis缓存+原子性+队列【干掉lock】

1.设计思路

生产者和消费者模式→流量削峰(异步的模式平滑处理请求)

思路同上,缓存和队列改成基于Redis的。

2. 分析

A. 引入Redis缓存和消息队列代替基于内存的缓存和队列,数据可以持久化解决了丢失问题。

B. Redis是单线程的,利用api自身的原子性,从而可以干掉lock锁。

C. 引入进程外的缓存Redis,从而可以把生产者和消费者解耦分离,可以作为两个单独的服务运行。

3. 压测结果

前提:原库存为10万,这里统计2s内可处理的并发数,以90%百分位为例,要求错误率为0。

代码分享:

初始化库存到redis缓存中

复制代码
    /// <summary>
    /// 后台任务-初始化库存到缓存中
    /// </summary>
    public class CacheBackService : BackgroundService
    {
        private IMemoryCache _cache;
        private StackExchange.Redis.IDatabase _redisDb;
        private IConfiguration _Configuration;

        public CacheBackService(IMemoryCache cache,RedisHelp redisHelp, IConfiguration Configuration)
        {
            _cache = cache;
            _redisDb = redisHelp.GetDatabase();
            _Configuration = Configuration;
        }

        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // EFCore的上下文默认注入的请求内单例的,而CacheBackService要注册成全局单例的
            // 由于二者的生命周期不同,所以不能相互注入调用,这里手动new一个EF上下文
            var optionsBuilder = new DbContextOptionsBuilder<ESHOPContext>();
            optionsBuilder.UseSqlServer(_Configuration.GetConnectionString("EFStr"));
            ESHOPContext context = new ESHOPContext(optionsBuilder.Options);
            IBaseService _baseService = new BaseService(context);

            //初始化库存信息,连临时写在这个位置,充当服务器启动的时候初始化
            var data = await _baseService.Entities<T_SeckillArticle>().Where(u => u.id == "300001").FirstOrDefaultAsync();
            //Redis缓存
                _redisDb.StringSet($"{data.articleId}-sCount", data.articleStockNum);
        }
    }
复制代码

下单接口

复制代码
        /// <summary>
        /// 04-Redis缓存+队列
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <param name="arcId">商品编号</param>
        /// <param name="totalPrice">订单总额</param>
        /// <returns></returns>
        public string POrder4(string userId, string arcId, string totalPrice)
        {
            try
            {
                //1. 直接自减1
                int iCount = (int)_redisDb.StringDecrement($"{arcId}-sCount", 1);
                if (iCount >= 0)
                {
                    //2. 将下单信息存到消息队列中
                    var orderNum = Guid.NewGuid().ToString("N");
                    _redisDb.ListLeftPush(arcId, $"{userId}-{arcId}-{totalPrice}-{orderNum}");

                    //3. 把部分订单信息返回给前端
                    return $"下单成功,订单信息为:userId={userId},arcId={arcId},orderNum={orderNum}";
                }
                else
                {
                    //卖完了
                    return "卖完了";
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
复制代码

基于redis队列的消费者

复制代码
            {
                Console.WriteLine("下面开始执行消费业务");
                using (ESHOPContext db = new ESHOPContext())
                {
                    RedisHelp redisHelp = new RedisHelp("localhost:6379");
                    var redisDB = redisHelp.GetDatabase();

                    while (true)
                    {
                        try
                        {
                            var data = (string)redisDB.ListRightPop("200001");
                            if (!string.IsNullOrEmpty(data))
                            {
                                List<string> tempData = data.Split('-').ToList();

                                {
                                    //1.扣减库存 --去掉状态追踪
                                    var sArctile = db.Set<T_SeckillArticle>().AsNoTracking().Where(u => u.id == "300001").FirstOrDefault();
                                    sArctile.articleStockNum = sArctile.articleStockNum - 1;
                                    db.Update(sArctile);

                                    //2. 插入订单信息
                                    T_Order tOrder = new T_Order();
                                    tOrder.id = Guid.NewGuid().ToString("N");
                                    tOrder.userId = tempData[0];
                                    tOrder.orderNum = tempData[3];
                                    tOrder.articleId = tempData[1];
                                    tOrder.orderTotalPrice = Convert.ToDecimal(tempData[2]);
                                    tOrder.addTime = DateTime.Now;
                                    tOrder.orderStatus = 0;
                                    db.Add<T_Order>(tOrder);
                                    int count = db.SaveChanges();

                                    //释放一下--否则报错
                                    db.Entry<T_SeckillArticle>(sArctile).State = EntityState.Detached;
                                    Console.WriteLine($"执行成功,条数为:{count},当前库存为:{ sArctile.articleStockNum}");

                                }

                            }
                            else
                            {
                                Console.WriteLine("暂时没有订单信息,休息一下");
                                Thread.Sleep(1000);
                            }
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine($"执行失败-{ex.Message}");
                        }
                    }
                }
            }
复制代码

(1). 1000并发,需要600ms,订单数量插入正确,库存扣减正确。

(2). 2000并发,需要1560ms,订单数量插入正确,库存扣减正确。

 

 

MS SQL巡检系列——检查数据库上一次DBCC CHECKDB的时间 - 潇湘隐者 - 博客园

mikel阅读(576)

来源: MS SQL巡检系列——检查数据库上一次DBCC CHECKDB的时间 – 潇湘隐者 – 博客园

BCC CHECKDB检查指定数据库中的所有对象的逻辑和物理完整性,具体请参考MSDN文档。我们必须定期对数据库做完整性检查(DBCC CHECKDB),以便能及时发现一些数据库损坏(Corruption)的情况。如果你的数据库长时间没有做DBCC CHECKDB,这样是做是不合理,并且很危险的。那么我们怎么检查数据库上一次做DBCC CHECKDB的时间呢? 可以通过DBCC DBINFO来获取上一次做DBCC CHECKDB时间,DBCC DBINFO (db_name) 显示数据库的结构信息 ,它是一个未公开的命令,官方没有相关文档。适用于SQL SERVER 2005以及以上版本。

DBCC DBINFO (‘YourSQLDba’) WITH TABLERESULTS ;

DBCC DBINFO () WITH TABLERESULTS ;

 

如下所示,你找到Field值为dbi_dbccLastKnownGood的记录,表示这个数据库在2016-05-13 01:30:11.560做了DBCC CHECKDB,如果没有做过DBCC CHECKDB命令,VALUE值为1900-01-01 00:00:00.000

clipboard

下面是巡检检查DBCC CHECKDB情况的脚本(只是巡检脚本的一部分)。此处定义的是至少一周必须做一次DBCC CHECKDB,否则提示那些数据库必须做完整性检查(DBCC CHECKDB)。具体情况,可以根据自身实际情况酌情处理。

CREATE TABLE #Db
  (
    DbName sysname
  )
  CREATE TABLE #Database
  (
    DbName sysname
  );

  INSERT INTO #Db
  SELECT name FROM sys.databases WHERE state_desc='ONLINE' AND database_id !=2;

  INSERT INTO #Database
  SELECT name FROM sys.databases WHERE state_desc='ONLINE' AND database_id !=2;

CREATE TABLE #DBCC_CHECKDB
(
    [DbName]        sysname  NULL   ,
    [ParentObject]  VARCHAR(120),
    [Object]        VARCHAR(120),
    [Field]         VARCHAR(60) ,
    [Value]         VARCHAR(120)
)

DECLARE @DbName   NVARCHAR(512);
DECLARE @SqlText  NVARCHAR(1024);

SET @DbName ='';
WHILE(1=1)

BEGIN
 SELECT TOP 1
      @DbName = DbName
    FROM #Db
    WHERE DbName > @DbName -- next Dbname greater than @dbname
    ORDER BY DbName -- dbName order

  -- exit loop if no more name greater than the last one used
    If @@rowcount = 0 Break

    PRINT @DbName;
SET @SqlText='DBCC DBINFO (''' + LTRIM(RTRIM(@DbName)) + ''') WITH TABLERESULTS '
INSERT INTO #DBCC_CHECKDB([ParentObject],[Object],[Field], [Value])
EXEC(@SqlText);

UPDATE #DBCC_CHECKDB SET DbName = @DbName WHERE DbName IS NULL;

 Delete Db
  From #Db Db WHERE DbName=@DbName;

END

--SELECT * FROM #DBCC_CHECKDB WHERE Field='dbi_dbccLastKnownGood';


SELECT  db.DbName ,
        dc.ParentObject ,
        dc.Object,
        dc.Field ,
        dc.Value ,
        CASE WHEN DATEDIFF(DAY, CAST(dc.Value AS DATE), GETDATE()) > 7
             THEN 'You need do a dbcc checkdb now'
             ELSE 'dbcc checkdb is done during the past seven day'
        END AS CheckStatus
FROM    #Database db
        INNER JOIN #DBCC_CHECKDB dc ON db.DbName = dc.DbName
WHERE   Field = 'dbi_dbccLastKnownGood';

DROP TABLE #Db;
DROP TABLE #Database;
DROP TABLE #DBCC_CHECKDB;

测试效果如下截图所示,定期对服务器做巡检,就能对所有服务器的情况有所了解,当然如果服务器很多的情况下,一台一台去检查也非常麻烦,我们通过MyDBA系统将数据从其它服务器采集过来,然后在报表里面展示,非常的节省时间。

image

MS SQL巡检系列——检查重复索引 - 潇湘隐者 - 博客园

mikel阅读(569)

来源: MS SQL巡检系列——检查重复索引 – 潇湘隐者 – 博客园

前言感想一时兴起,突然想写一个关于MS SQL的巡检系列方面的文章,因为我觉得这方面的知识分享是有价值,也是非常有意义的。一方面,很多经验不足的人,对于巡检有点茫然,不知道要从哪些方面巡检,另外一方面,网上关于MS SQL巡检方面的资料好像也不是特别多。写这个系列只是一个分享,自己的初衷是一个知识梳理、总结提炼过程,有些知识和脚本也不是原创,文章很多地方也是融入了自己的一些想法和见解的,不足和肤浅之处肯定也非常多,抛砖引玉,也希望大家提意见和建议、补充,指正其中的不足之处。Stay Hungry Stay Foolish!

 

在SQL Server数据库中,有可能存在重复的索引(Duplicate Indexes),这个不仅影响性能(INSERT、UPDATE、DELETE时带来额外的IO开销,当数据库维护,索引重组时也会带来额外的开销),而且占用空间。数据库存在重复索引(Duplicate Indexes)的原因是多方面的,很多时候、很多事情不是你所能完全掌控的,除非你所管理的数据库非常规范,权限控制、脚本发布非常严格、流程化。暂且不说这些,那么怎么在数据库巡检过程找出这些重复的索引(Duplicate Indexes)呢? 下面分享一个我在Premier Proactive Services中发现一个的脚本(做了一些修改和调整)。

 

我们以AdventureWorks2014数据库为例,如下所示,表[Person].[Address]下有4个索引,如下所示

 

clipboard

 

假设某个二愣子在这个表的字段StateProvinceID上创建了下面重复索引,IX_Address_N1 与IX_Address_StateProvinceID是一个重复索引。


CREATE INDEX IX_Address_N1 ON [Person].[Address](StateProvinceID);

 

那么我们执行下面脚本就能找到这个重复的索引,如下所示

;WITH    IndexColumns
          AS ( SELECT DISTINCT
                      SCHEMA_NAME(o.schema_id)     AS SchemaName    ,
                      OBJECT_NAME(o.object_id)     AS TableName     ,
                      i.name                       AS IndexName     ,
                      o.object_id                  AS [Object_ID]   ,
                      i.index_id                   AS Index_ID      ,
                      i.type_desc                 AS IndexType      ,
                      ( SELECT    CASE key_ordinal
                                    WHEN 0 THEN NULL
                                    ELSE '[' + COL_NAME(k.object_id,
                                                        column_id) + '] '
                                         + CASE WHEN is_descending_key = 1
                                                THEN 'Desc'
                                                ELSE 'Asc'
                                           END
                                  END AS [data()]
                        FROM      sys.index_columns  k WITH(NOLOCK)
                        WHERE     k.object_id = i.object_id
                                  AND k.index_id = i.index_id
                        ORDER BY  key_ordinal ,
                                  column_id
                      FOR
                        XML PATH('')
                      ) AS IndexColumns ,
                        CASE WHEN i.index_id = 1
                             THEN ( SELECT  '[' + name + ']' AS [data()]
                                    FROM    sys.columns (NOLOCK) AS c
                                    WHERE   c.object_id = i.object_id
                                            AND c.column_id NOT IN (
                                            SELECT  column_id
                                            FROM    sys.index_columns (NOLOCK)
                                                    AS kk
                                            WHERE   kk.object_id = i.object_id
                                                    AND kk.index_id = i.index_id )
                                    ORDER BY column_id
                                  FOR
                                    XML PATH('')
                                  )
                             ELSE ( SELECT  '[' + COL_NAME(k.object_id,
                                                           column_id) + ']' AS [data()]
                                    FROM    sys.index_columns k WITH(NOLOCK)
                                    WHERE   k.object_id = i.object_id
                                            AND k.index_id = i.index_id
                                            AND is_included_column = 1
                                            AND k.column_id NOT IN (
                                            SELECT  column_id
                                            FROM    sys.index_columns kk
                                            WHERE   k.object_id = kk.object_id
                                                    AND kk.index_id = 1 )
                                    ORDER BY key_ordinal ,
                                            column_id
                                  FOR
                                    XML PATH('')
                                  )
                        END AS IndexInclude
               FROM     sys.indexes  i WITH(NOLOCK)
                        INNER JOIN sys.objects o WITH(NOLOCK) ON i.object_id = o.object_id
                        INNER JOIN sys.index_columns ic  WITH(NOLOCK ) ON ic.object_id = i.object_id
                                                              AND ic.index_id = i.index_id
                        INNER JOIN sys.columns c WITH(NOLOCK) ON c.object_id = ic.object_id
                                                              AND c.column_id = ic.column_id
               WHERE    o.type = 'U'
                        AND i.index_id <> 0  -- 0 = 堆
                        AND i.type <> 3         -- 3 = XML
                        AND i.type <> 5         -- 5 = 聚集列存储索引(SQL 2014~ SQL 2016)
                        AND i.type <> 6         -- 6 = 非聚集列存储索引(SQL 2014~ SQL 2016)
                        AND i.type <> 7         -- 7 = 非聚集哈希索引(SQL 2014~ SQL 2016)
               GROUP BY o.schema_id ,
                        o.object_id ,
                        i.object_id ,
                        i.name ,
                        i.index_id ,
                        i.type_desc
             ),
        DuplicatesTable
          AS ( SELECT   ic1.SchemaName    ,
                        ic1.TableName     ,
                        ic1.IndexName     ,
                        ic1.[Object_ID]   ,
                        ic2.IndexName AS DuplicateIndexName ,
                        ic1.IndexType   ,
                        CASE WHEN ic1.index_id = 1
                             THEN ic1.IndexColumns + ' (Clustered)'
                             WHEN ic1.IndexInclude = '' THEN ic1.IndexColumns
                             WHEN ic1.IndexInclude IS NULL THEN ic1.IndexColumns
                             ELSE ic1.IndexColumns + ' INCLUDE ' + ic1.IndexInclude
                        END AS IndexCols ,
                        ic1.index_id
               FROM     IndexColumns ic1
                        JOIN IndexColumns ic2 ON ic1.object_id = ic2.object_id
                                                 AND ic1.index_id < ic2.index_id
                                                 AND ic1.IndexColumns = ic2.IndexColumns
                                                 AND ( ISNULL(ic1.IndexInclude, '') = ISNULL(ic2.IndexInclude,
                                                              '')
                                                       OR ic1.index_id = 1
                                                     )
             )
    SELECT  SchemaName ,
            TableName ,
            IndexName ,
            DuplicateIndexName ,
            IndexType,
            IndexCols ,
            Index_ID ,
          Object_ID ,
          0 AS IsXML
    FROM    DuplicatesTable dt
    ORDER BY 1 , 2 ,3

 

clipboard

 

注意,关于重复索引(Duplicate Indexes)表示存在的索引除了名字不一样外, 索引所在字段以及索引字段顺序都是一样的。An index is considered to be a duplicate if it references the same column and ordinal position as another index in the same database。 这个脚本是找出一模一样的索引,如果你创建下面索引,索引字段一样,但是有包含列字段不一样,那么这个脚本会将这个索引视为不一样的索引。有兴趣可以自己试试。

 

CREATE INDEX IX_Address_N2 ON [Person].[Address](StateProvinceID) INCLUDE (City);

 

另外关于XML索引的重复索引,可以使用下面脚本检查。

--Use the below T-SQL script to generate the complete list of duplicate XML indexes in a given database:

;WITH    XMLTable
          AS ( SELECT   OBJECT_NAME(x.object_id) AS TableName ,
                        SCHEMA_NAME(o.schema_id) AS SchemaName ,
                        x.object_id ,
                        x.name ,
                        x.index_id ,
                        x.using_xml_index_id ,
                        x.secondary_type ,
                        CONVERT(NVARCHAR(MAX), x.secondary_type_desc) AS secondary_type_desc ,
                        ic.column_id
               FROM     sys.xml_indexes x ( NOLOCK )
                        JOIN sys.objects o ( NOLOCK ) ON x.object_id = o.object_id
                        JOIN sys.index_columns (NOLOCK) ic ON x.object_id = ic.object_id
                                                              AND x.index_id = ic.index_id
             ),
        DuplicatesXMLTable
          AS ( SELECT   x1.SchemaName ,
                        x1.TableName ,
                        x1.name AS IndexName ,
                        x2.name AS DuplicateIndexName ,
                        x1.secondary_type_desc AS IndexType ,
                        x1.index_id ,
                        x1.object_id ,
                        ROW_NUMBER() OVER ( ORDER BY x1.SchemaName, x1.TableName, x1.name, x2.name ) AS seq1 ,
                        ROW_NUMBER() OVER ( ORDER BY x1.SchemaName DESC, x1.TableName DESC, x1.name DESC, x2.name DESC ) AS seq2 ,
                        NULL AS inc
               FROM     XMLTable x1
                        JOIN XMLTable x2 ON x1.object_id = x2.object_id
                                            AND x1.index_id < x2.index_id
                                            AND x1.using_xml_index_id = x2.using_xml_index_id
                                            AND x1.secondary_type = x2.secondary_type
             )
    SELECT  SchemaName ,
            TableName ,
            IndexName ,
            DuplicateIndexName ,
            IndexType  ,
            Index_ID ,
            [Object_ID] ,
            1 AS IsXML
    FROM    DuplicatesXMLTable dtxml
    ORDER BY 1 ,
             2 ,
             3;

 

在每个库跑一次这个脚本,就能将所有的重复的索引(Duplicate Indexes)全部找出,但是当手头服务器、数据库特别多时,这个工作也是一个体力活,可以将这个常规工作自动化,避免重复劳动,我将这个集成在MyDBA工具里面,只需要点击一下鼠标,就可以帮助我自动处理这些工作。

MS SQL巡检系列——检查外键字段是否缺少索引 - 潇湘隐者 - 博客园

mikel阅读(701)

来源: MS SQL巡检系列——检查外键字段是否缺少索引 – 潇湘隐者 – 博客园

前言感想一时兴起,突然想写一个关于MS SQL的巡检系列方面的文章,因为我觉得这方面的知识分享是有价值,也是非常有意义的。一方面,很多经验不足的人,对于巡检有点茫然,不知道要从哪些方面巡检,另外一方面,网上关于MS SQL巡检方面的资料好像也不是特别多。写这个系列只是一个分享,自己的初衷是一个知识梳理、总结提炼过程,有些知识和脚本也不是原创,文章很多地方融入了自己的一些想法和见解的,不足和肤浅之处肯定也非常多,抛砖引玉,也希望大家提意见和建议、补充,指正其中的不足之处。Stay Hungry Stay Foolish!

 

MS SQL巡检系列——检查重复索引

MS SQL巡检系列——检查外键字段是否缺少索引

MS SQL巡检系列——检查数据库上一次DBCC CHECKDB的时间

 

对于绝大部分情况,外键字段都有必要建立对应的索引(注意,外键约束并不会自动建立索引),关于外键字段为什么要建立索引?下面从几个简单的例子来分析一下。我们先准备测试环境数据。

CREATE TABLE PRIMARY_TB
(
    PRODUCT_CD        VARCHAR(12)      ,
    PRODUCT_DATE    DATE             ,
    PRODUCT_DESC    VARCHAR(120)     ,
    CONSTRAINT PK_PRIMARY_TB  PRIMARY KEY CLUSTERED (PRODUCT_CD)
);


SET NOCOUNT ON;
GO
DECLARE @Index INT=1;


BEGIN TRAN
WHILE @Index <= 3000
BEGIN

    INSERT INTO dbo.PRIMARY_TB
    SELECT 'Prd' + CAST(@Index AS VARCHAR(4)), GETDATE() - CEILING(RAND()*200), 'production description' + CAST(@Index AS VARCHAR(4));

    SET @Index +=1;
END

COMMIT;



CREATE TABLE FK_TB
(
    FK_ID            INT IDENTITY(1,1),
    SALES_REGION    VARCHAR(32),
    SALES_CITY        VARCHAR(32),
    PRODUCT_CD        VARCHAR(12),
    SALIES_SUM        INT,
    CONSTRAINT PK_FK_TB PRIMARY KEY CLUSTERED (FK_ID)
)
GO

ALTER TABLE [dbo].[FK_TB]  WITH CHECK ADD  CONSTRAINT [FK_PRIMARY_TB_PRODUCT_CD] FOREIGN KEY([PRODUCT_CD])
REFERENCES [dbo].[PRIMARY_TB] ([PRODUCT_CD]) ON  DELETE CASCADE;
GO


SET NOCOUNT ON;
GO
DECLARE @Index INT=1;

BEGIN TRAN
WHILE @Index <=1000000
BEGIN
    INSERT INTO FK_TB
    SELECT 'REGION'+CAST(CEILING(RAND()*20) AS VARCHAR(2)), CAST(CEILING(RAND()*300) AS VARCHAR(3)),'Prd'+ CAST(CEILING(RAND()*3000) AS VARCHAR(8)),CEILING(RAND()*100000);

    SET @Index +=1;

END

COMMIT;


UPDATE STATISTICS dbo.PRIMARY_TB WITH FULLSCAN;
UPDATE STATISTICS dbo.FK_TB WITH FULLSCAN;
GO

 

1: 外键字段建立索引,在主表与子表JOIN操作时能提高性能,减少IO操作。

DBCC DROPCLEANBUFFERS;
GO
DBCC FREEPROCCACHE;
GO
SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT  p.PRODUCT_CD ,
        p.PRODUCT_DATE ,
        f.SALES_REGION ,
        f.SALES_CITY ,
        f.SALIES_SUM
FROM    dbo.PRIMARY_TB p
        INNER JOIN dbo.FK_TB f ON p.PRODUCT_CD = f.PRODUCT_CD
WHERE p.PRODUCT_CD ='Prd131';
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

 

如下截图所示,如果外键字段缺少索引,这两个表关联查询时,子表就会走扫描(此处测试是聚集索引扫描),如果子表非常大(例如此处案例所示),IO开销就比较大。

clipboard

clipboard

 

我们对外键约束字段PRODUCT_CD建立下面非聚集索引IDX_FK_TB,然后对比两者的执行计划和IO开销

CREATE INDEX IDX_FK_TB ON dbo.FK_TB(PRODUCT_CD);

DBCC DROPCLEANBUFFERS;
GO
DBCC FREEPROCCACHE;
GO

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT  p.PRODUCT_CD ,
        p.PRODUCT_DATE ,
        f.SALES_REGION ,
        f.SALES_CITY ,
        f.SALIES_SUM
FROM    dbo.PRIMARY_TB p
        INNER JOIN dbo.FK_TB f ON p.PRODUCT_CD = f.PRODUCT_CD
WHERE p.PRODUCT_CD ='Prd131'

SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

clipboard

clipboard

 

你会发现执行计划从原来的聚集索引扫描(Clustered Index Scan)变成了索引查找(Index Seek),IO的减少也是非常明显的。因为这里仅仅是测试数据,复杂的生产环境,性能的提升有可能比这更加明显。

 

2:如果外键约束为CASCADE(on update/delete)时,则当修改被引用行(referenced row)时,所有引用行(referencing rows )也必须修改(更新或级联删除)。外键列上的索引能减小锁的粒度和范围,从而提高效率和性能。如下所示:

 

我们先看看缺少索引的情况。

DROP INDEX IDX_FK_TB ON dbo.FK_TB;



DBCC DROPCLEANBUFFERS;
GO
DBCC FREEPROCCACHE;
GO

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

DELETE FROM dbo.PRIMARY_TB WHERE PRODUCT_CD IN ('Prd132','Prd133')
GO
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

clipboard

clipboard

CREATE INDEX IDX_FK_TB ON dbo.FK_TB(PRODUCT_CD);

GO

DBCC DROPCLEANBUFFERS;

GO

DBCC FREEPROCCACHE;

GO

SET STATISTICS IO ON;

SET STATISTICS TIME ON;

DELETE FROM dbo.PRIMARY_TB WHERE PRODUCT_CD IN ('Prd134','Prd135')

GO

SET STATISTICS IO OFF;

SET STATISTICS TIME OFF;

clipboard

clipboard

 

3:如果外键关系为NO ACTION(on update/delete)时,那么被引用的行不能被删除,因为这个操作会导致引用行变成“孤立”。删除之前,数据库会为了有效地查找、定位行,外键列上创建索引也非常有帮助。

 

上面肤浅的构造了简单案例,并对比外键约束字段拥有索引和缺少索引时,SQL的执行计划和IO的差异,那么接下来,我们进入正题,巡检的时候,我们必须通过脚本找到数据库里面外键字段没有索引的相关表和信息,并生成对应的创建索引的脚本。如下所示。

/*
One or more tables found, with foreign key constraint defined but no supporting indexes created on the foreign key columns.

SQL Server doesnt put an index on foreign key columns by default and indexing foreign key fields in referencing tables is not required.

Foreign key columns usage must evaluated to determine whether or not indexing this column will help up increase the current
workloads performance by enhancing join performance, reducing table locking (for full table scans) while cascading updates and
deletes, etc.

*/

;
WITH    FKTable
          AS ( SELECT   SCHEMA_NAME(po.schema_id) AS 'parent_schema_name' ,
                        OBJECT_NAME(fkc.parent_object_id) AS 'parent_table_name' ,
                        OBJECT_NAME(constraint_object_id) AS 'constraint_name' ,
                        SCHEMA_NAME(ro.schema_id) AS 'referenced_schema' ,
                        OBJECT_NAME(referenced_object_id) AS 'referenced_table_name' ,
                        ( SELECT    '[' + COL_NAME(k.parent_object_id,
                                                   parent_column_id) + ']' AS [data()]
                          FROM      sys.foreign_key_columns (NOLOCK) AS k
                                    INNER JOIN sys.foreign_keys  (NOLOCK) ON k.constraint_object_id = object_id
                                                              AND k.constraint_object_id = fkc.constraint_object_id
                          ORDER BY  constraint_column_id
                        FOR
                          XML PATH('')
                        ) AS 'parent_colums' ,
                        ( SELECT    '[' + COL_NAME(k.referenced_object_id,
                                                   referenced_column_id) + ']' AS [data()]
                          FROM      sys.foreign_key_columns (NOLOCK) AS k
                                    INNER JOIN sys.foreign_keys  (NOLOCK) ON k.constraint_object_id = object_id
                                                              AND k.constraint_object_id = fkc.constraint_object_id
                          ORDER BY  constraint_column_id
                        FOR
                          XML PATH('')
                        ) AS 'referenced_columns'
               FROM     sys.foreign_key_columns fkc ( NOLOCK )
                        INNER JOIN sys.objects po ( NOLOCK ) ON fkc.parent_object_id = po.object_id
                        INNER JOIN sys.objects ro ( NOLOCK ) ON fkc.referenced_object_id = ro.object_id
               WHERE    po.type = 'U'
                        AND ro.type = 'U'
               GROUP BY po.schema_id ,
                        ro.schema_id ,
                        fkc.parent_object_id ,
                        constraint_object_id ,
                        referenced_object_id
             ),

        /* Index Columns */
        IndexColumnsTable
          AS ( SELECT   SCHEMA_NAME(o.schema_id) AS 'schema_name' ,
                        OBJECT_NAME(o.object_id) AS TableName ,
                        ( SELECT    CASE key_ordinal
                                      WHEN 0 THEN NULL
                                      ELSE '[' + COL_NAME(k.object_id,
                                                          column_id) + ']'
                                    END AS [data()]
                          FROM      sys.index_columns (NOLOCK) AS k
                          WHERE     k.object_id = i.object_id
                                    AND k.index_id = i.index_id
                          ORDER BY  key_ordinal ,
                                    column_id
                        FOR
                          XML PATH('')
                        ) AS cols
               FROM     sys.indexes (NOLOCK) AS i
                        INNER JOIN sys.objects o ( NOLOCK ) ON i.object_id = o.object_id
                        INNER JOIN sys.index_columns ic ( NOLOCK ) ON ic.object_id = i.object_id
                                                              AND ic.index_id = i.index_id
                        INNER JOIN sys.columns c ( NOLOCK ) ON c.object_id = ic.object_id
                                                              AND c.column_id = ic.column_id
               WHERE    o.type = 'U'
                        AND i.index_id > 0
               GROUP BY o.schema_id ,
                        o.object_id ,
                        i.object_id ,
                        i.name ,
                        i.index_id ,
                        i.type
             ),
        FKWithoutIndexTable
          AS ( SELECT   fk.parent_schema_name AS SchemaName ,
                        fk.parent_table_name AS TableName ,
                        fk.referenced_schema AS ReferencedSchemaName ,
                        fk.referenced_table_name AS ReferencedTableName ,
                        fk.constraint_name AS ConstraintName ,
                        fk.referenced_columns AS Referenced_Columns ,
                        fk.parent_colums AS Parent_Columns
               FROM     FKTable fk
               WHERE    NOT EXISTS ( SELECT 1
                                     FROM   IndexColumnsTable ict
                                     WHERE  fk.parent_schema_name = ict.schema_name
                                            AND fk.parent_table_name = ict.TableName
                                            AND fk.parent_colums = LEFT(ict.cols,
                                                              LEN(fk.parent_colums)) )
             )
    SELECT  @@SERVERNAME AS InstanceName ,
            DB_NAME() AS DatabaseName ,
            SchemaName ,
            TableName ,
            Parent_Columns ,
            ReferencedSchemaName ,
            ReferencedTableName ,
            Referenced_Columns ,
            ConstraintName
    INTO    #ForeignKeyWithOutIndex
    FROM    FKWithoutIndexTable
    ORDER BY DatabaseName ,
            SchemaName ,
            TableName;




--输出临时表数据
SELECT  *
FROM    #ForeignKeyWithOutIndex;


--生成外键字段缺少的索引,请抽查、检验,确认后批量执行
SELECT  'CREATE INDEX IX_' + LTRIM(RTRIM(TableName)) + '_'
        + SUBSTRING(Parent_Columns, 2, LEN(Parent_Columns) - 2) + '  ON '
        + LTRIM(RTRIM(SchemaName)) + '.' + LTRIM(RTRIM(TableName)) + '('
        + Parent_Columns + ');'
FROM    #ForeignKeyWithOutIndex;


--删除临时表
DROP TABLE #ForeignKeyWithOutIndex;

在创建这些索引前最好检查、确认一下,外键字段创建索引能提高性能,但是肯定也要特殊的场景和上下文不适合,所以最好根据实际情况决定。索引创建之后,通过监控工具监控一下数据库性能、等待事件的变化。

 

参考资料:

http://stackoverflow.com/questions/3650690/should-every-sql-server-foreign-key-have-a-matching-index

http://sqlblog.com/blogs/greg_low/archive/2008/07/29/indexing-foreign-keys-should-sql-server-do-that-automatically.aspx

http://www.sqlskills.com/blogs/kimberly/when-did-sql-server-stop-putting-indexes-on-foreign-key-columns/

 

SQL Server中INNER JOIN与子查询IN的性能测试 - 潇湘隐者 - 博客园

mikel阅读(715)

来源: SQL Server中INNER JOIN与子查询IN的性能测试 – 潇湘隐者 – 博客园

这个月碰到几个人问我关于“SQL SERVER中INNER JOIN 与 IN两种写法的性能孰优孰劣?”这个问题。其实这个概括起来就是SQL Server中INNER JOIN与子查询孰优孰劣(IN是子查询的实现方式之一,本篇还是只对比INNER JOIN与子查询IN的性能,如果展开INNER JOIN与子查询性能对比,范围太大了,没法一一详述)。下面这篇文章,我们就INNER JOIN与子查询IN这两种写法孰优孰劣,在不同场景下进行一下测试对比一下,希望能解答你心中的疑惑。

 

下面例子以AdventureWorks2014为测试场景,测试表为Sales.SalesOrderHeader与Sales.SalesOrderDetail。 如下所示:


DBCC FREEPROCCACHE;
GO
DBCC DROPCLEANBUFFERS;
GO

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT  h.* FROM
Sales.SalesOrderHeader h
WHERE SalesOrderID IN ( SELECT SalesOrderID FROM Sales.SalesOrderDetail)

clip_image001

 

clip_image002

 

DBCC FREEPROCCACHE;
GO
DBCC DROPCLEANBUFFERS;
GO
SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT h.* FROM Sales.SalesOrderHeader h
INNER JOIN Sales.SalesOrderDetail d ON h.SalesOrderID = d.SalesOrderID

 

如下所示,两种写法的SQL的实际执行计划是几乎一致。而且对比IO开销也是一致。cpu time 与elapsed time 有所差别,这个是因为两者返回的数据有所差别的缘故(SQL 1 返回 31465行数据, SQL 2返回 121317行数据),两者在逻辑上实际上是不一致的。因为重复数据的缘故。撇开这个不谈,光从性能上来考察两种,它们几乎是一模一样。没有优劣之分。

 

clip_image003

 

clip_image004

 

如果有人对上面的重复数据不明白的话,下面做个简单的例子演示给大家看看。如下所示,截图中INNER JOIN就会有重复数据。

 

CREATE TABLE P
(
    PID    INT ,
    Pname  VARCHAR(24)
)

INSERT INTO dbo.P
SELECT 1, 'P1' UNION ALL
SELECT 2, 'P2' UNION ALL
SELECT 3, 'P3'


CREATE TABLE dbo.C
(
    CID       INT ,
    PID       INT ,
    Cname  VARCHAR(24)
)

INSERT INTO dbo.c
SELECT 1, 1, 'C1' UNION ALL
SELECT 2, 1, 'C2' UNION ALL
SELECT 3, 2, 'C3' UNION ALL
SELECT 3, 3, 'C4'


clip_image005

 

其实下面SQL在逻辑上才是相等的,它们的实际执行计划与IO是一样的。没有优劣之分。

 

SELECT  h.* FROM
Sales.SalesOrderHeader h
WHERE SalesOrderID IN ( SELECT SalesOrderID FROM Sales.SalesOrderDetail);


SELECT DISTINCT h.* FROM Sales.SalesOrderHeader h
INNER JOIN Sales.SalesOrderDetail d ON h.SalesOrderID = d.SalesOrderID;

 

clip_image006

 

那么我们再来看另外一个例子,测试一下两者的性能差别。如下所示

 

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT  C.*
FROM    Sales.Customer C
        INNER JOIN Person.Person P ON C.PersonID = P.BusinessEntityID;


SELECT  C.*
FROM    Sales.Customer C
WHERE  C.PersonID IN ( SELECT Person.Person.BusinessEntityID
                                     FROM   Person.Person );

 

 

INNER JOIN与子查询IN的实际执行计划对比的百分比为66% VS 34% , 子查询IN的性能还比 INNER JOIN的性能要好一些. IO几乎无差别,cpu time 与elapsed time的对比情况来看,子查询IN的性能确实要好一些。

 

这个是因为子查询IN在这个上下文环境中,它使用右半连接(Right Semi Join)方式的Hash Match,即一个表中返回的行与另一个表中数据行进行不完全联接查询(查找到匹配的数据行就返回,不再继续查找)。那么可以肯定的是,在这个场景(上下文)中,子查询IN这种方式的SQL的性能比INNER JOIN 这种写法的SQL要好。

 

clip_image007

clip_image008

 

 

那么我们再来看一个INNER JOIN性能比子查询(IN)要好的案例。如下所示,我们先构造测试数据。

 

CREATE TABLE P
(
    P_ID    INT IDENTITY(1,1),
    OTHERCOL        CHAR(500),
    CONSTRAINT PK_P PRIMARY KEY(P_ID)
)
GO

BEGIN TRAN
DECLARE @I INT = 1
WHILE @I<=10000
BEGIN
    INSERT INTO P VALUES (NEWID())
    SET @I = @I+1
    IF (@I%500)=0
    BEGIN
        IF @@TRANCOUNT>0
        BEGIN
            COMMIT
            BEGIN TRAN
        END
    END
END
IF @@TRANCOUNT>0
BEGIN
    COMMIT
END
GO


CREATE TABLE C
(
    C_ID  INT IDENTITY(1,1) ,
    P_ID   INT  FOREIGN KEY REFERENCES P(P_ID),
    COLN  CHAR(500),
    CONSTRAINT PK_C  PRIMARY KEY (C_ID)
)




SET NOCOUNT ON;

DECLARE @I INT = 1
WHILE @I<=1000000
BEGIN
    INSERT INTO C VALUES ( CAST(RAND()*10 AS INT)+1,  NEWID())
    SET @I = @I+1
END
GO

 

构造完测试数据后,我们对比下两者的性能差异

 

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT C.* FROM dbo.C C
INNER JOIN dbo.P  P ON C.P_ID = P.P_ID
WHERE P.P_ID=8


SELECT * FROM dbo.C
WHERE P_ID IN (SELECT P_ID FROM dbo.P WHERE P_ID=8)

 

clip_image009

clip_image010

 

增加对应的索引后,这个性能差距更更明显。 如下截图所示

 


USE [AdventureWorks2014]
GO
CREATE NONCLUSTERED INDEX [IX_C_N1]
ON [dbo].[C] ([P_ID])
INCLUDE ([C_ID],[COLN])
GO

clip_image011

 

在生产环境遇到一个案例, 两个视图使用INNER JOIN 与 IN 两种写法,在性能上差距很大。 使用子查询IN的性能比使用INNER JOIN的性能要好很多。如下截图所示。因为视图里面涉及多表。这样肯定导致执行计划非常复杂,导致SQL用INNER JOIN 的写法在性能上没有用子查询IN的写法要快

 

clip_image012

 

其实一部分情况下,INNER JOIN 与 子查询IN都是等价的。因为SQL Server优化器已经足够聪明,能够进行一些内部转换,生成等价的计划。但是在某一些特殊场景下,各有优劣。不能武断的就说INNER JOIN在性能上要比子查询IN要好。一定要结合上下文环境具体来谈性能优劣。否则没有多大意义。另外,子查询可以分为相关子查询和无关子查询,对于无关子查询来说,Not In子句比较常见,但Not In潜在会带来两种问题,结果不正确和性能问题,具体可以参考在SQL Server中为什么不建议使用Not In子查询

MS SQL 事务日志管理小结 - 潇湘隐者 - 博客园

mikel阅读(606)

来源: MS SQL 事务日志管理小结 – 潇湘隐者 – 博客园

本文是对SQL Server事务日志的总结,文章有一些内容和知识来源于官方文档或一些技术博客,本文对引用部分的出处都有标注。

 

事务日志介绍

SQL Server中,事务日志是数据库的重要组件,如果系统出现故障,则可能需要使用事务日志将数据库恢复到一致状态。每个SQL Server数据库都拥有自己的事务日志,用于记录所有事务以及每个事务对数据库所做的修改。那么数据库的哪些操作会记录在事务日志中呢?具体一点的说,这些操作包括:

  • 每个事务的开始和结束。

  • 每次数据修改(插入、更新或删除)。 这包括系统存储过程或数据定义语言 (DDL) 语句对包括系统表在内的任何表所做的更改。

  • 每次分配或释放区和页。

  • 创建或删除表或索引。

另外,像SELECT这样的操作是不会记录在事务日志当中的。如果你想对事务日志记录信息有一个直观的认识,那么你可以在测试环境做一些SELECT、INSERT、UPDATE、DDL等操作,然后使用ApexSQL Log这款工具查看具体的事务日志记录信息。

USE YourSQLDba;

GO

CREATE TABLE dbo.TEST(ID  INT);

GO

INSERT INTO dbo.TEST SELECT 100;

GO

SELECT * FROM dbo.TEST;

GO

UPDATE dbo.TEST SET ID=101;

GO

DELETE FROM dbo.TEST WHERE ID=101;

GO

如上所示,像DDL、DML操作都会记录在事务日志当中,但是SELECT是不会记录在事务日志当中(当然SELECT INTO除外,其实SELECT INTO在事务日志里面转化为了CREATE TABLE形式)。另外,需要注意: 事务日志并不是审计跟踪。也就是说事务日志并不能完全替代审计跟踪。它不提供对数据库做出改变的审计跟踪;它不保持对数据库已执行命令的记录,只有数据如何改变的结果。其实很多对事务日志了解不深入的人都以为事务日志可以代替审计跟踪(曾经有项目经理想让我从事务日志当中挖掘出谁误删了数据,其实事务日志只会记录那个账号删除了记录,并不会记录客户端信息,所以不能定位到谁删除了数据)。如下所示,我们多了一个DROP TABLE操作。你会看到跟上面不一样的结果。

USE YourSQLDba;

GO

CREATE TABLE dbo.TEST(ID  INT);

GO

INSERT INTO dbo.TEST SELECT 100;

GO

SELECT * FROM dbo.TEST;

GO

UPDATE dbo.TEST SET ID=101;

GO

DELETE FROM dbo.TEST WHERE ID=101;

GO

DROP TABLE dbo.Test;

GO

这篇博客transactionlog中有一张图,描述了一个更新操作的流程中,事务日志在这个流程中的位置以及作用。想必看过这张图后,大家在大脑中会对事务日志的功能作用有一个初步的形象认识。

其实这张图还包含了很多隐藏的重要信息,下面我们一一来述说一下:

预写式日志(Write-Ahead Logging

 

什么是预写式日志呢? 其实其核心思想就是在变化的数据写入到数据库之前,将相关日志记录信息先写入到日志. SQL Server的预写式日志(Write-Ahead Logging)机制保证修改的描述(例如日志记录)会在数据本身修改写入数据文件前写入,会写入磁盘上的事务日志文件。它是SQL Server保证事务持久性(Durability)的基本机制。一个日志记录会包含已提交事务或未提交事务的详细信息,在数据被事务修改的不同情况下,可能已经写入数据文件或还没来得及写入数据文件,这取决于检查点是否已发生。

浅谈SQL Server中的事务日志(二)—-事务日志在修改数据时的角色 这篇博客有深入浅出的介绍(如下所示):

Write-Ahead Logging的核心思想是:在数据写入到数据库之前,先写入到日志.

因为对于数据的每笔修改都记录在日志中,所以将对于数据的修改实时写入到磁盘并没有太大意义,即使当SQL Server发生意外崩溃时,在恢复(recovery)过程中那些不该写入已经写入到磁盘的数据会被回滚(RollBack),而那些应该写入磁盘却没有写入的数据会被重做(Redo)。从而保证了持久性(Durability)。

     但WAL不仅仅是保证了原子性和持久性。还会提高性能.

     硬盘是通过旋转来读取数据,通过WAL技术,每次提交的修改数据的事务并不会马上反映到数据库中,而是先记录到日志.在随后的CheckPoint和Lazy Writer中一并提交,如果没有WAL技术则需要每次提交数据时写入数据库……

官方文档SQL Server 事务日志体系结构和管理指南介绍如下(个人对翻译做了一下调整,也增加了一点点内容):

要了解预写日志的工作方式,了解如何将修改的数据写入磁盘很重要。SQL Server维护一个缓冲区缓存(buffer cache),在必须检索数据时从其中读取数据页。 在缓冲区缓存中修改页后,不会将其立即写回磁盘;而是将其标记为“脏”数据。在将数据页物理写入磁盘之前,这些脏数据可以多次被修改。 对于每次逻辑写入,都会在日志缓存(log cache)中插入一条事务日志记录记录这些修改。在将关联的脏页从缓冲区缓存中删除并写入磁盘之前,必须将这条些日志记录写入磁盘。检查点进程定期在缓冲区高速缓存中扫描包含来自指定数据库的页的缓冲区,然后将所有脏页写入磁盘。 CHECKPOINT 可创建一个检查点,在该点保证全部脏页都已写入磁盘,从而在以后的恢复过程中节省时间。

将修改后的数据页从高速缓冲存储器写入磁盘的操作称为刷新页。 SQL Server具有一个逻辑,它可以在写入关联的日志记录前防止刷新脏页。 日志记录将在提交事务时写入磁盘。

 

检查点作用

 

检查点将脏数据页从当前数据库的缓冲区高速缓存刷新到磁盘上。这最大限度地减少了数据库完整恢复时必须处理的活动日志,减少的崩溃恢复需要的时间。其实CheckPoint是为了优化IO和减少Recovery时间 在完整恢复时,需执行下列操作:

  • 前滚系统停止之前尚未刷新到磁盘上的日志记录修改信息。
  • 回滚与未完成的事务(如没有 COMMIT 或 ROLLBACK 日志记录的事务)相关联的所有修改。

检查点操作

 

检查点在数据库中执行下列过程:

  • 将记录写入日志文件,标记检查点的开始。

  • 将为检查点记录的信息存储在检查点日志记录链内。

  • 记录在检查点中的一条信息是第一条日志记录的日志序列号 (LSN),它必须存在才能成功进行数据库范围内的回滚。 该 LSN 称为“最小恢复 LSN”(“MinLSN”)。 MinLSN 是下列各项中的最小者:

o   检查点开始的 LSN。

o   最早的活动事务起点的 LSN。

o   尚未传递给分发数据库的最早的复制事务起点的 LSN。

o   检查点记录还包含所有已修改数据库的活动事务的列表。

  • 如果数据库使用简单恢复模式,检查点则标记在 MinLSN 前重用的空间。
  • 将所有脏日志和脏数据页写入磁盘。
  • 将标记检查点结束的记录写入日志文件。
  • 将这条链起点的 LSN 写入数据库引导页。

导致检查点的活动

 

 

下列情形下将出现检查点:

  • 显式执行 CHECKPOINT 语句。 用于连接的当前数据库中出现检查点。
  • 在数据库中执行了最小日志记录操作,例如,在使用大容量日志恢复模式的数据库中执行大容量复制操作。
  • 已经使用 ALTER DATABASE 添加或删除了数据库文件。
  • 通过 SHUTDOWN 语句或通过停止 SQL Server (MSSQLServer) 服务停止了 SQL Server 实例。 任一操作都会在 SQL Server 实例的每个数据库中生成一个检查点。
  • SQL Server 实例在每个数据库内定期生成自动检查点,以减少实例恢复数据库所需的时间。
  • 进行了数据库备份。
  • 执行了需要关闭数据库的活动。 例如,AUTO_CLOSE 设置为 ON ,并且关闭了数据库的最后一个用户连接,或者执行了需要重新启动数据库的数据库选项更改。

事务日志物理结构

 

 

SQL Server数据库中的事务日志可以有一个或多个事务日志文件。当存在多个事务日志文件时,这些日志文件也只能顺序调用,并不能并行使用,因此使用多个日志文件并不会带来性能上的提升(后面内容会展开讨论这个)。其实,如果你对ORACLE当中联机重做日志体系结构非常熟悉的话,多个事务日志文件就相当于多个redo log file,不同的是,ORACLE下面的redo log可以实现多路复用(日志组可以有一个或多个同样的日志成员redo log file,多个日志成员的原因是防止日志文件组内某个日志文件损坏后及时提供备份,所以同一组的日志成员一般内容信息相同,但是存放位置不同)。一般会将同一组的不同日志成员文件放到不同的磁盘或不同的裸设备上。以提高安全性。SQL Server似乎没有这个架构设计。另外,ORACLE的REDO 与UNDO在结构设计上是分开的。而SQL Server可以通过事务日志进行REDO和UNDO操作。

事务日志逻辑结构

 

从逻辑结构上看,SQL Server对于日志文件的管理,是将逻辑上一个ldf文件划分成多个逻辑上的虚拟日志文件(virtual log files,简称VLFs).以便于管理。SQL Server事务日志按逻辑运行,就好像事务日志是一串日志记录一样。每条日志记录由一个日志序列号 (LSN) 标识。 每条新日志记录均写入日志的逻辑结尾处,并使用一个比前面记录的 LSN 更高的 LSN。 日志记录按创建时的串行序列存储。 每条日志记录都包含其所属事务的 ID。对于每个事务,与事务相关联的所有日志记录通过使用可提高事务回滚速度的向后指针挨个链接在一个链中。 虚拟日志文件没有固定大小,且物理日志文件所包含的虚拟日志文件数不固定。 数据库引擎在创建或扩展日志文件时动态选择虚拟日志文件的大小。 数据库引擎尝试维护少量的虚拟文件。 在扩展日志文件后,虚拟文件的大小是现有日志大小和新文件增量大小之和。 管理员不能配置或设置虚拟日志文件的大小或数量。但是如果设置日志文件的增量过小,则会产生过多的VLFS,也就是日志文件碎片,过多的日志文件碎片会拖累SQL Server性能.因此,指定合适的日志文件初始大小和增长,是减少日志碎片最关键的部分.

事务日志是一种回绕的文件。 例如,假设有一个数据库,它包含一个分成四个虚拟日志文件的物理日志文件。 当创建数据库时,逻辑日志文件从物理日志文件的始端开始。 新日志记录被添加到逻辑日志的末端,然后向物理日志的末端扩张。 日志截断将释放记录全部在最小恢复日志序列号 (MinLSN) 之前出现的所有虚拟日志。 MinLSN 是成功进行数据库范围内回滚所需的最早日志记录的日志序列号。 示例数据库中的事务日志的外观与下图所示相似。

当逻辑日志的末端到达物理日志文件的末端时,新的日志记录将回绕到物理日志文件的始端。

 

上面关于事务日志的虚拟日志循环覆盖使用是否有点眼熟的感觉,这个跟ORACLE下REDO LOG的循环覆盖使用的理念是一模一样的。只不过是不同的概念和不同的实现方式。

 

事务日志功能

 

 

 

事务日志有啥功能呢?关于事务日志的功能,详细具体内容可以参考官方文档事务日志 (SQL Server),里面已经详细介绍了事务日志的几个功能,在此不做展开。

事务日志支持以下操作:

  • 恢复个别的事务。

  • 在SQL Server启动时恢复所有未完成的事务。

  • 将还原的数据库、文件、文件组或页前滚至故障点。

  • 支持事务复制。

  • 支持高可用性和灾难恢复解决方案: AlwaysOn 可用性组、数据库镜像和日志传送。

 

事务日志截断

 

什么是事务日志截断呢? 在介绍事务日志截断前,我们必须先了解一下MinLSN、活动日志(Actvie Log)等概念。

最小恢复LSN(Minimum Recovery LSN(MinLSN))概念

  MinLSN是在还未结束的事务记录在日志中最小的LSN号,MinLSN是下列三者之一的最小值:

  • CheckPoint的开始LSN

  • 还未结束的事务在日志的最小LSN

  • 尚未传递给分发数据库的最早的复制事务起点的 LSN.

从MinLSN到日志的逻辑结尾处,则称为活动日志(Active Log)。日志文件中从 MinLSN 到最后写入的日志记录这一部分称为日志的活动部分,或者称为活动日志(Active log)。 这是进行数据库完整恢复所需的日志部分。 永远不能截断活动日志的任何部分。所有的日志记录都必须从 MinLSN 之前的日志部分截断。也就是说永远不能截断活动日志的任何部分。

下图显示了具有两个活动事务的结束事务日志的简化版本。 检查点记录已压缩成单个记录。

LSN 148 是事务日志中的最后一条记录。 在处理 LSN 147 处记录的检查点时,Tran 1 已经提交,而 Tran 2 是唯一的活动事务。 这就使 Tran 2 的第一条日志记录成为执行最后一个检查点时处于活动状态的事务的最旧日志记录。 这使 LSN 142(Tran 2 的开始事务记录)成为 MinLSN。

活动日志必须包括所有未提交事务的每一部分。如果应用程序开始执行一个事务但未提交或回滚,将会阻止数据库引擎推进 MinLSN。 这可能会导致两种问题:

    如果系统在事务执行了许多未提交的修改后关闭,以后重新启动时,恢复阶段所用的时间将比“恢复间隔”选项指定的时间长得多。

    因为不能截断 MinLSN 之后的日志部分,日志可能变得很大。 即使数据库使用的是简单恢复模式,这种情况也有可能出现,在简单恢复模式下,每次执行自动检查点操作时通常都会截断事务日志。

日志截断其实指从SQL Server数据库的逻辑事务日志中删除不活动的虚拟日志文件,释放逻辑日志中的空间以便物理事务日志重用这些空间。 如果事务日志从不截断,它最终将填满分配给物理日志文件的所有磁盘空间。 但是,在截断日志前,必须执行检查点操作。检查点将当前内存中已修改的页(称为“脏页”)和事务日志信息从内存写入磁盘。 执行检查点时,事务日志的不活动部分将标记为可重用。 此后,日志截断可以释放不活动的部分。有关检查点的详细信息,请参阅数据库检查点 (SQL Server)。

关于日志截断,必须定期截断事务日志,防止其占满分配给物理日志文件的磁盘空间。日志截断并不减小物理日志文件的大小。 若要减少物理日志文件的物理大小,则必须收缩日志文件。

日志截断会在下面事件后自动进行截断:

    简单恢复模式下,在检查点之后发生。

    在完整恢复模式或大容量日志恢复模式下,如果自上一次备份后生成检查点,则在日志备份后进行截断(除非是仅复制日志备份)。

   CHECKPOINT only truncates the transaction log (marks the VLF for reuse) only in simple recovery model. In Full recovery, you have to take log backup.

实际上,日志截断会由于多种原因发生延迟。 查询 sys.databases 目录视图的 log_reuse_wait 和 log_reuse_wait_desc 列,了解哪些因素(如果存在)阻止日志截断。 下表对这些列的值进行了说明:

Log_reuse_wait  Log_reuse_wait_desc  说明
0 NOTHING 当前有一个或多个可重复使用的虚拟日志文件。
1 CHECKPOINT 自上次日志截断之后,尚未生成检查点,或者日志头尚未跨一个虚拟日志文件移动。 (所有恢复模式)

这是日志截断延迟的常见原因。 有关详细信息,请参阅数据库检查点 (SQL Server)

2 LOG_BACKUP 在截断事务日志前,需要进行日志备份。 (仅限完整恢复模式或大容量日志恢复模式)

完成下一个日志备份后,一些日志空间可能变为可重复使用。

3 ACTIVE_BACKUP_OR_RESTORE 数据备份或还原正在进行(所有恢复模式)。

如果数据备份阻止了日志截断,则取消备份操作可能有助于解决备份直接导致的此问题。

4 ACTIVE_TRANSACTION 事务处于活动状态(所有恢复模式):

一个长时间运行的事务可能存在于日志备份的开头。 在这种情况下,可能需要进行另一个日志备份才能释放空间。 请注意,长时间运行的事务将阻止所有恢复模式下的日志截断,包括简单恢复模式,在该模式下事务日志一般在每个自动检查点截断。

延迟事务。 “延迟的事务 ”是有效的活动事务,因为某些资源不可用,其回滚受阻。 有关导致事务延迟的原因以及如何使它们摆脱延迟状态的信息,请参阅延迟的事务 (SQL Server)

长时间运行的事务也可能会填满 tempdb 的事务日志。 Tempdb 由用户事务隐式用于内部对象,例如用于排序的工作表、用于哈希的工作文件、游标工作表,以及行版本控制。 即使用户事务只包括读取数据(SELECT 查询),也可能会以用户事务的名义创建和使用内部对象, 然后就会填充 tempdb 事务日志。

5 DATABASE_MIRRORING 数据库镜像暂停,或者在高性能模式下,镜像数据库明显滞后于主体数据库。 (仅限完整恢复模式)

有关详细信息,请参阅数据库镜像 (SQL Server)

6 REPLICATION 在事务复制过程中,与发布相关的事务仍未传递到分发数据库。 (仅限完整恢复模式)

有关事务复制的信息,请参阅 SQL Server Replication

7 DATABASE_SNAPSHOT_CREATION 正在创建数据库快照。 (所有恢复模式)

这是日志截断延迟的常见原因,通常也是主要原因。

8 LOG_SCAN 发生日志扫描。 (所有恢复模式)

这是日志截断延迟的常见原因,通常也是主要原因。

9 AVAILABILITY_REPLICA 可用性组的辅助副本正将此数据库的事务日志记录应用到相应的辅助数据库。 (完整恢复模式)

有关详细信息,请参阅:AlwaysOn 可用性组概述 (SQL Server)

10 仅供内部使用
11 仅供内部使用
12 仅供内部使用
13 OLDEST_PAGE 如果将数据库配置为使用间接检查点,数据库中最早的页可能比检查点 LSN 早。 在这种情况下,最早的页可以延迟日志截断。 (所有恢复模式)

有关间接检查点的信息,请参阅数据库检查点 (SQL Server)

14 OTHER_TRANSIENT 当前未使用此值。

 

事务日志收缩

 

有时候我们监控告警会发现事务日志出现暴增的情况,那么此时就必须对是事务日志进行收缩,不管数据库处于那种恢复模式,简单、完整模式。都可以按下面流程进行收缩。

1:查看对应数据库事务日志的逻辑名称(name),后续操作需要用到。

SELECT  database_id ,        name ,        type_desc ,        physical_nameFROM    sys.master_filesWHERE   database_id = DB_ID(‘YourSQLDba’)    AND type_desc=’LOG’

2: 使用DBCC SQLPERF查看事务日志空间使用情况统计信息:

      DBCC SQLPERF (LOGSPACE)

     如果对应数据库的Log Space Used(%)的值较小,那么就可以收缩事务日志。

  3:执行类似下面的收缩事务日志文件语句。

USE YourSQLDba;

GO

DBCC SHRINKFILE(‘YourSQLDba_Log’, 128);

  如果Log Space Used(%)很小,而收缩效果又不佳,那么一般是因为日志截断延迟造成,一般可以通过下面脚本检查原因,大部分情况是因为等待LOG_BACKUP缘故。所以你对事务日志做一次备份后,再进行收缩即可解决。

SELECT  name ,        log_reuse_wait  ,        log_reuse_wait_descFROM    sys.databasesWHERE   database_id = DB_ID(‘YourSQLDba’);  backup log [YourSQLDba] to disk = ‘M:\DB_BACKUP\LOG_BACKUP\YourSQLDba_[2018-01-11_06h37m26_Thu]_logs.TRN’ with noInit, checksum, name = ‘YourSQLDba:15h40: M:\DB_BACKUP\LOG_BACKUP\YourSQLDba_[2018-01-11_06h37m26_Thu]_logs.TRN’

 

 

增加事务日志文件

SQL Server数据库中的事务日志可以有一个或多个事务日志文件,但是即使有多个事务日志文件,也不能并行写入多个事务日志文件,数据库引擎还是会串行使用多个事务日志文件。也就是说大多数场景,多个事务日志文件其实并没有什意义,那么它存在的意义是什么呢?例如,当你当前磁盘告警,事务日志无法继续增长,你需要在其他磁盘新增一个事务日志文件,让数据库继续顺畅运行。个人觉得多个事务日志文件确实是一个很鸡肋的东西。Paul S. Randal在“了解SQL Server的日志记录和恢复”中明确指出:不要创建多个的日志文件,因为它不会导致性能增益。

下面是如何增加一个事务日志文件的样例:

USE [master]GOALTER DATABASE [YourSQLDba] ADD LOG FILE ( NAME = N’YourSQLDba_Log2′, FILENAME = N’D:\SQL_LOG\YourSQLDba_Log1.LDF’ , SIZE = 65536KB , MAXSIZE = 55296KB , FILEGROWTH = 10%)GO

 

删除事务日志文件

既然可以增加事务日志文件,那么当然也可以删除事务日志文件,但是这个删除操作是有限制的。主日志文件(primary log)是不能删除的。如果你删除primary log就会报“不能从数据库中删除主数据文件或主日志文件。”,下面我们来测试一下。

准备测试环境如下:

USE master;GOCREATE DATABASE [TEST] CONTAINMENT = NONE ON  PRIMARY ( NAME = N’TEST’, FILENAME = N’D:\SQL_DATA\TEST.mdf’ , SIZE = 100MB , MAXSIZE = 40GB, FILEGROWTH = 64MB ) LOG ON ( NAME = N’TEST_log’ , FILENAME = N’D:\SQL_LOG\TEST_LOG_1.ldf’ , SIZE = 20MB , MAXSIZE = 40MB , FILEGROWTH = 10MB),( NAME = N’TEST_log2′, FILENAME = N’D:\SQL_LOG\TEST_LOG_2.ldf’ , SIZE = 20MB , MAXSIZE = 20GB , FILEGROWTH = 10MB)GO BACKUP DATABASE [TEST] TO  DISK = N’D:\DB_BACKUP\Test.bak’         WITH NOFORMAT, NOINIT,          NAME = N’TEST-Full Database Backup’,        SKIP, NOREWIND, NOUNLOAD,  STATS = 10;GO  USE TEST;GOSELECT * INTO mytest FROM sys.objects;GOINSERT INTO mytestSELECT * FROM mytestGO 12 DBCC SQLPERF(LOGSPACE) DBCC LOGINFO(‘TEST’)

 

注意,此时DBCC LOGINFO显示FileId=3的日志文件对应的虚拟日志(VLF)的Status为2,此时删除事务日志文件会提示文件无法删除,因为Status=2意味着VLF不能被覆盖和重用。

Status = 2 means that VLF can’t be reused (overwritten) at this time and it doesn’t necessarily mean that VLF is still active and writing transactions to that VLF. As Jonathan already mentioned, it means that the VLF is waiting for backup/REPL/Mirroring etc..

USE master;

GO

ALTER DATABASE TEST REMOVE FILE TEST_log2

备份事务日志后,你会发现FileId=3的日志文件对应的虚拟日志(VLF)的Status变为了0,那么此时就可以移除事务日志文件了。

BACKUP LOG TEST TO DISK = ‘D:\SQL_LOG\Test.Trn’

GO

DBCC LOGINFO(‘TEST’)

GO

USE master;

GO

ALTER DATABASE TEST REMOVE FILE TEST_log2

如果是生产环境或者在上述备份事务日志后,对应日志文件的VLF的状态仍然为2,那么可以用收缩日志文件和备份事务日志循环处理,直至对应日志文件下所有的VLF状态全部为0,就可以删除事务日志文件。

USE TEST;

GO

DBCC SHRINKFILE(TEST_log2);

BACKUP LOG TEST TO DISK = ‘D:\SQL_LOG\Test.Trn’

注意,主日志文件(primary log)是不能删除的,如下测试所示:

USE master;

GO

ALTER DATABASE TEST REMOVE FILE TEST_log

Msg 5020, Level 16, State 1, Line 35

The primary data or log file cannot be removed from a database.

但是当你需要规划存储路径、移动事务日志文件时,你可以使用折中的方法将主事务日志文件(primary log)移动到其它目录。如下所示:

1: 将当前数据库脱机;

ALTER DATABASE TEST SET OFFLINE;

2: 修改数据库的事务日志位置

ALTER DATABASE TEST

MODIFY FILE

(

NAME = N’TEST_log’

, FILENAME = N’E:\SQL_LOG\TEST_LOG_1.ldf’

)

3: 手工将事务日志文件移动到上面位置

4:将数据库联机操作。

ALTER DATABASE TEST SET ONLINE;

另外,如何判断那个日志文件是主事务日志文件?目前来说,我只能这样判断, sys.master_files当中,file_id最小值对应的日志文件为主事务日志文件。用脚本判断如下:

SELECT  f.database_id            AS database_id  ,        DB_NAME(f.database_id)   AS database_name,        MIN(f.file_id)           AS primary_log_id ,        f.type_desc              AS type_desc    FROM    sys.master_files  fWHERE  f.database_id= DB_ID(‘databasename’) AND  type = 1GROUP BY f.database_id,f.type_desc;

 

另外,你也可以用下面脚本查出哪些数据库拥有两个或以上事务日志。

SELECT  f.database_id    AS database_id  ,        d.name           AS database_name,        f.type_desc      AS type_desc    ,        COUNT(*)         AS log_countFROM    sys.master_files  fINNER  JOIN sys.databases d ON f.database_id = d.database_idWHERE   type = 1GROUP BY f.database_id ,         f.type_desc,         d.nameHAVING  COUNT(*) >= 2;

 

参考资料

https://docs.microsoft.com/zh-cn/sql/relational-databases/sql-server-transaction-log-architecture-and-management-guide#physical_arch

https://docs.microsoft.com/zh-cn/sql/relational-databases/logs/the-transaction-log-sql-server#FactorsThatDelayTruncation

https://docs.microsoft.com/zh-cn/sql/relational-databases/logs/database-checkpoints-sql-server

https://technet.microsoft.com/zh-cn/library/2009.02.logging.aspx

http://www.cnblogs.com/CareySon/archive/2012/02/13/2349751.html

http://www.cnblogs.com/CareySon/p/3315041.html

http://www.cnblogs.com/CareySon/archive/2012/02/17/2355200.html