SQL Server 执行计划操作符详解(1)——断言(Assert)

本文详细介绍了SQL Server执行计划中的Assert操作符,用于验证条件,如引用完整性、Check约束和外键约束。通过实例展示了Assert如何处理插入操作,以及在子查询中的应用。理解Assert对于SQL性能分析和优化至关重要。

前言:

 

很多很多地方对于语句的优化,一般比较靠谱的回复即使——把执行计划发出来看看。当然那些只看语句就说如何如何改代码,我一直都是拒绝的,因为这种算是纯蒙。根据本人经验,大量的性能问题单纯从语句来看很难发现瓶颈,同一个语句,由于环境的不同,差距非常大,所以比较合适的还是分析执行计划。

那么对于执行计划,一般使用图形化执行计划就差不多了,但是用过的人也有一些疑惑,里面的图标(称为操作符)并不非常直观。所以从本文开始,会整理一些不怎么常见但由比较重要的操作符并进行解释,对于那些表扫描、索引扫描、聚集索引扫描、索引查找、聚集索引查找这些非常常见的操作符,暂时不打算介绍。

只有了解一些重要且常见的操作符,才能对语句进行准确有效的性能分析和优化。

本系列文章预计包含下面操作符:    

  1. 断言:Assert (英文版本图形化界面的名字,中文版本中XML格式的执行计划和TEXT格式的执行计划的名字。下同)    
  2. 串联:Concatenation   
  3. 计算标量:Compute Scalar  
  4. 键查找:Key Lookup      
  5. 假脱机:Spools      
  6. 表假脱机:Lazy Spool   
  7. 索引假脱机:Index Spool      
  8. 行计数假脱机:Row CountSpool
  9. 流聚合:Stream Aggregate  
  10. 排序:Sort     
  11. 合并联接:Merge Join 
  12. 合并间隔:Merge Interval   
  13. 拆分、折叠:Split,Collapse

 接下来从断言开始介绍。原文出处:http://blog.csdn.net/dba_huangzj/article/details/50261747


断言:


Assert运算符是一个物理运算符。在执行计划中,如果为中文版图形化执行计划,被称为“断言”,在英文版及非图形化执行计划中显示为Assert。

其图标为:Assert 运算符图标

Assert 运算符用于验证条件。例如,验证引用完整性或确保标量子查询返回一行。对于每个输入行,Assert 运算符都要计算执行计划的 Argument 列中的表达式。如果此表达式的值为 NULL,则通过 Assert 运算符传递该行,并且查询执行将继续。如果此表达式的值非空,则将产生相应的错误。


断言与Check约束:

 

</

#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ @Author : DaoLao @Date : 2025/9/12 @Version : 1.0.0 @Description : 断言控制模块,提供统一的断言处理功能 """ import re from typing import Any from requests import Response from commons.assertion import assert_operator from commons.mysql_control import MysqlController from commons.enums import PatternType, GlobalRegistry, SqlType from commons.extract_util import json_extract from commons.load_module import load_module_function from commons.registry_manager import execute_function from commons.utils.config_manager import global_config from commons.models import AssertOperator from commons.utils.exceptions import ConfigError, BaseError from commons.utils.log_handler import logger def assert_control(response: Response, assert_data: dict[str, Any]): """ 执行断言控制逻辑入口 """ sql_data_list = assert_data.get("sql_data", []) sql_result = _execute_sql_list(sql_data_list, response) if sql_data_list else {} assertions = assert_data.get("assertions", []) total_assertions = len(assertions) logger.info(f" - 开始执行断言,数量:{total_assertions}个") for index, assert_item in enumerate(assertions, 1): desc = assert_item.get("desc", f"断言 {index}") logger.info(f" [{index}/{total_assertions}] {desc}") source = assert_item.get("source", "").lower() operator = assert_item.get("operator") expected = assert_item.get("expected") _execute_single_assertion(assert_item, response, source, operator, expected, sql_result) # 添加分隔线(除了最后一个断言) if index < total_assertions: logger.info("-" * 50) def _execute_sql_list(sql_data_list: list[dict[str, str]], response: Response): """ 执行断言前的SQL操作 """ db = MysqlController(global_config.env_config["database"]) # 获取已初始化的数据库实例 json_pattern = re.compile(PatternType.JSON) # JSON模式 sql_pattern = re.compile(PatternType.SQL_TYPE, re.IGNORECASE) # SQL类型模式,忽略大小写 # 遍历SQL列表 sql_result = {} for sql_data in sql_data_list: alias = sql_data["alias"] sql = sql_data["sql"] params = [] # 遍历SQL语句中所有匹配JSON模式的部分,提取JSON数据并替换占位符 # 该代码块用于处理SQL中包含的JSON路径表达式,将其替换为实际的JSON值 matches = json_pattern.finditer(sql) for match in matches: # 匹配到JSON,则进行提取 jsonpath = match.group(1) params.append(json_extract(jsonpath, response)) sql = sql.replace(match.group(0), "%s") # 判断sql类型,并执行相应的操作 match_type = sql_pattern.match(sql) if not match_type: raise ConfigError(f" -> SQL类型 {sql} 不支持!") sql_type = match_type.group(1) if sql_type == SqlType.SELECT: result = db.query(sql, tuple(params)) sql_result.update({alias: result}) logger.info(f" - 查询结果:{result}") elif sql_type in SqlType.DELETE: result = db.execute(sql, tuple(params)) sql_result.update({alias: result}) logger.info(f" - 影响行数:{result}") else: raise BaseError(f" -> 配置中的SQL类型SqlType和" f"正则表达式{PatternType.SQL_TYPE}匹配的类型种类不相同!") return sql_result def _execute_single_assertion(assert_item: dict[str, Any], response: Response, source: str, operator: str, expected: Any, sql_result: dict[str, Any]): """ 执行单个断言 """ try: operator_item = AssertOperator(operator).name except Exception as e: error_message = f" -> 断言操作符 {operator} 不支持!" logger.error(error_message) raise ConfigError(error_message) from e context = {"assert_item": assert_item, "response": response, "sql_result": sql_result} actual = execute_function(GlobalRegistry.AssertType, source, context) # 执行断言 assert_util(operator_item, actual, expected) def assert_util(operator_item, actual, expected): """ :param operator_item: 操作判断类型 :param actual: 实际值 :param expected: 预期值 :return: 布尔值,True表示断言通过,False表示断言失败 """ logger.info(f" - 实际: {actual}") logger.info(f" - 预期: {expected}") logger.info(f" - 判断类型:{operator_item}") operator_mapping = load_module_function(assert_operator) operator_func = operator_mapping[operator_item] try: operator_func(actual, expected) logger.info(f" ✅ <断言成功> {actual} {operator_item} {expected}") return True except Exception as e: error_message = f" ❌ 断言失败 {actual} {operator_item} {expected}:{str(e)}" logger.error(error_message) raise AssertionError(error_message) from e
最新发布
11-02
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值