首页 文章 精选 留言 我的

精选列表

搜索[编译原理],共10000篇文章
优秀的个人博客,低调大师

RPA与AI Agent的自动化原理区别

一段浏览器任务能够被RPA顺利执行,并不表示换成AI Agent就会更好;反过来,网页一有变化就要修改流程,也不意味着固定自动化完全没有价值。比较RPA与AI Agent时,需要看下一步动作由谁决定,以及系统凭什么确认任务继续执行的条件已经满足。这个区别会直接影响开发、调试和维护工作。 > RPA指按事先编排的步骤、条件与分支执行任务的流程自动化;AI Agent指由模型理解目标、结合环境反馈决定下一步动作和工具使用的执行方式。两者的核心差别在于“下一步由谁决定”。 ![](https://developer.qcloudimg.com/http-save/audit-12748179/0b6fde1d7caddf966e66dd86b54d0f6c.png) ## 一、RPA:动作、条件与分支都事先规定 典型的RPA流程会事先规定动作、条件与分支。浏览器进入某个页面后,程序定位输入框、填入数据、等待结果,再执行下一项操作。微软的浏览器自动化文档展示了基于网页元素和选择器的操作方式,也提供条件判断和等待等动作,可参考 [网页自动化动作](https://learn.microsoft.com/en-us/power-automate/desktop-flows/actions-reference/webautomation)。因而,把RPA理解为只能重复鼠标坐标,并不准确。 ## 二、固定流程的优势在于路径可以提前检查 固定流程的优势在于路径可以提前检查。假设任务是把已整理好的商品字段录入测试后台,输入格式和页面结构都明确,那么字段从哪里取、缺少数据时怎么办、保存后检查什么,都可以逐项定义。某一步失败时,也容易根据运行记录找到具体动作。代价是页面和业务条件改变后,需要有人维护这些定义。 ![](https://developer.qcloudimg.com/http-save/audit-12748179/8a35b8dea79c1a2437eb5a0d003a7d40.png) ## 三、AI Agent 依据目标和环境反馈选择路径 AI Agent则可以依据目标和环境反馈决定工具使用与执行路径。Anthropic对工作流和智能体的区分,核心就在预设路线与模型动态控制之间,参见 [工作流与智能体](https://www.anthropic.com/engineering/building-effective-agents)。这种动态能力适合部分路径不确定的任务,但并不意味着系统可以省略输入条件、工具权限和结果验收。 ## 四、理解交给模型,执行交给流程 仍以商品资料处理为例,如果不同供应商提供的描述格式不一致,模型可以帮助理解信息、提出字段映射,再把确认后的结构化结果交给固定流程录入。页面数据缺少关键属性时,系统应报告缺口或请求确认,而不能为了让流程继续运行自行补造规格。此时AI承担理解与判断,RPA承担可重复执行,两者并没有互相排斥。 ## 五、按错误性质安排异常处理 异常处理也应围绕错误性质安排。选择器失效,需要检查页面结构;模型理解错字段,需要回看输入和判断依据;网络超时,则应检查请求和等待条件。写入任务尤其要区分“提交失败”和“提交成功但没有收到反馈”。例如录入商品资料时,可以用任务记录中的商品编号查验后台结果,确认记录不存在后再重试,不能仅凭页面转圈就再次点击保存。 ## 六、测试要比较同一组样本的完整过程 测试时,不应只比较演示视频中的速度。应让两种方案处理同一组经过授权的样本,分别记录准备时间、执行时间、人工修改和异常恢复。模型方案的工具调用与输入上下文也需要保留,否则结果虽然生成了,团队却无法解释某次动作为什么不同。固定方案则应记录流程版本,避免页面变化后把新旧结果混在一起。 ![](https://developer.qcloudimg.com/http-save/audit-12748179/b86694d722e52974523efa60e0d0ee48.png) ## RPA 与 AI Agent 对照表 |对比项|RPA 或固定流程|AI Agent| | --- | --- | --- | |下一步由谁决定|事先编排好的动作、条件与分支|模型结合目标与页面反馈动态决定| |适合的任务|规则清楚、页面结构相对稳定|输入变化较大、路径需要按内容判断| |前期投入|逐项定义字段来源、判断条件与校验方式|准备输入条件、工具权限与结果验收规则| |异常定位|按运行记录定位到具体动作|需保留工具调用与输入上下文才能解释| |记录要求|记录流程版本,避免新旧结果混在一起|记录模型与上下文,避免结果无法复现| |常见分工|承担可重复的执行与校验|承担理解与判断,把结果交给固定流程| ## 七、先按任务特点选,再决定投入 如果流程规则清楚、页面相对稳定,先把固定自动化做好;如果输入变化较大、操作路径需要根据内容判断,可以让智能体承担那一小段判断工作。真正值得投入的方案,是在现有任务中减少了明确的人工环节,并且出了问题能够恢复。把任务拆到足以区分理解、执行和验证的粒度,往往比先决定必须使用哪一种技术更容易落地。 --- 资料说明:本文依据微软、Anthropic 等厂商公开的技术文档整理,核对日期为 2026 年 9 月 16 日,未进行客户端性能实测。

优秀的个人博客,低调大师

深入理解 MCP 协议:从原理到工程实践

**一、MCP 介绍** **1.1 什么是 MCP** MCP(Model Context Protocol,模型上下文协议)由 Anthropic 于去年 11 月正式提出。 从本质上来说,MCP 是一种技术协议,是智能体(Agent)开发过程中共同约定的一种规范。这就好比秦始皇的“书同文、车同轨”,在统一的规范下,各方的协作效率得以大幅提高,最终提升智能体 Agent 的整体开发效率。 **1.2 MCP 解决的核心痛点** 总的来说,MCP 解决的最大痛点,是 Agent 开发中调用外部工具的技术门槛过高的问题。 能够调用外部工具,是大模型进化为智能体 Agent 的关键。如果不能使用外部工具,大模型只能是一个简单的聊天机器人。由于底层技术限制,大模型本身无法直接与外部工具通信。因此,Function Calling 的思路是创建一个外部函数(Function)作为中介:一边传递大模型的请求,另一边调用外部工具,最终让大模型能够间接调用外部工具。 Function Calling 是一项非常出色的技术设计,自诞生以来一直被业内奉为圭臻。但其存在的问题是,编写外部函数的工作量过大。一个简单的外部函数往往需要上百行代码;为了让大模型“认识”这些函数,还需要额外为每个函数编写 JSON Schema 格式的功能说明;此外,还需精心设计提示词模板,才能提高 Function Calling 响应的准确率。 MCP 的目标,就是在 Agent 开发过程中,让大模型更加便捷地调用外部工具。 **1.3 MCP 的设计思路** 为了让大模型更便捷地调用外部工具,MCP 提出了“车同轨、书同文”的方案,统一 Function Calling 的运行规范: **统一名称:**MCP 将大模型运行环境称作 MCP Client(MCP 客户端),将外部函数运行环境称作 MCP Server(MCP 服务器)。 **统一规范:**统一 MCP 客户端和服务器的运行规范,并要求双方按照既定的提示词模板进行通信。 这种规范最大的好处在于,可以避免 MCP 服务器的重复开发。例如,查询天气、网页爬取、查询本地 MySQL 数据库等通用需求,只需一人开发好服务器,其他人即可直接复制到项目中使用,无需每次都单独编写。 **1.4 MCP 的生态与工具** “车同轨、书同文”的规范极大地促进了全球 AI 开发者的协作。很快,GitHub 上出现了海量的 MCP 服务器,涵盖 SQL 数据库检索、网页浏览与爬取、命令行操作、数据分析与机器学习建模等各个领域。 现在,只要本地运行的大模型支持 MCP 协议(即安装了相关库),仅需几行代码即可接入这些海量外部工具,Agent 的开发门槛瞬间降低。 这种旨在提高大模型 Agent 开发效率的技术协议,与 HTTP 等网络信息交换协议的目标一致,都是通过提高协作效率来提升开发效率。 MCP 客户端不仅支持 Claude 模型,也支持任意本地模型、在线大模型或 IDE。例如,Cursor 正式接入 MCP 后,不仅能快速编写 MCP 服务器,更能一键连接成百上千的开源 MCP 服务器,让大模型快速接入海量工具,大幅加快 Agent 开发进度。 **二、MCP 客户端 Client 开发流程** **2.1 编写基础 MCP 客户端** 以下是一个基础的 MCP 客户端实现代码: ```javascript import asyncio import os import json from typing import Optional from contextlib import AsyncExitStack from openai import OpenAI from dotenv import load_dotenv from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client # 加载 .env 文件,确保 API Key 受到保护 load_dotenv() class MCPClient: def __init__(self): """初始化 MCP 客户端""" self.exit_stack = AsyncExitStack() self.openai_api_key = os.getenv("OPENAI_API_KEY") # 读取 OpenAI API Key self.base_url = os.getenv("BASE_URL") # 读取 BASE YRL self.model = os.getenv("MODEL") # 读取 model if not self.openai_api_key: raise ValueError("❌ 未找到 OpenAI API Key,请在 .env 文件中设置 OPENAI_API_KEY") self.client = OpenAI(api_key=self.openai_api_key, base_url=self.base_url) # 创建OpenAI client self.session: Optional[ClientSession] = None self.exit_stack = AsyncExitStack() async def connect_to_server(self, server_script_path: str): """连接到 MCP 服务器并列出可用工具""" is_python = server_script_path.endswith('.py') is_js = server_script_path.endswith('.js') if not (is_python or is_js): raise ValueError("服务器脚本必须是 .py 或 .js 文件") command = "python" if is_python else "node" server_params = StdioServerParameters( command=command, args=[server_script_path], env=None ) # 启动 MCP 服务器并建立通信 stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) self.stdio, self.write = stdio_transport self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write)) await self.session.initialize() # 列出 MCP 服务器上的工具 response = await self.session.list_tools() tools = response.tools print("\n已连接到服务器,支持以下工具:", [tool.name for tool in tools]) async def process_query(self, query: str) -> str: """ 使用大模型处理查询并调用可用的 MCP 工具 (Function Calling) """ messages = [{"role": "user", "content": query}] response = await self.session.list_tools() available_tools = [{ "type": "function", "function": { "name": tool.name, "description": tool.description, "input_schema": tool.inputSchema } } for tool in response.tools] # print(available_tools) response = self.client.chat.completions.create( model=self.model, messages=messages, tools=available_tools ) # 处理返回的内容 content = response.choices[0] if content.finish_reason == "tool_calls": # 如何是需要使用工具,就解析工具 tool_call = content.message.tool_calls[0] tool_name = tool_call.function.name tool_args = json.loads(tool_call.function.arguments) # 执行工具 result = await self.session.call_tool(tool_name, tool_args) print(f"\n\n[Calling tool {tool_name} with args {tool_args}]\n\n") # 将模型返回的调用哪个工具数据和工具执行完成后的数据都存入messages中 messages.append(content.message.model_dump()) messages.append({ "role": "tool", "content": result.content[0].text, "tool_call_id": tool_call.id, }) # 将上面的结果再返回给大模型用于生产最终的结果 response = self.client.chat.completions.create( model=self.model, messages=messages, ) return response.choices[0].message.content return content.message.content async def chat_loop(self): """运行交互式聊天循环""" print("\n🤖 MCP 客户端已启动!输入 'quit' 退出") while True: try: query = input("\n你: ").strip() if query.lower() == 'quit': break response = await self.process_query(query) # 发送用户输入到 OpenAI API print(f"\n🤖 OpenAI: {response}") except Exception as e: print(f"\n⚠️ 发生错误: {str(e)}") async def cleanup(self): """清理资源""" await self.exit_stack.aclose() async def main(): if len(sys.argv) ") sys.exit(1) client = MCPClient() try: await client.connect_to_server(sys.argv[1]) await client.chat_loop() finally: await client.cleanup() if __name__ == "__main__": import sys asyncio.run(main()) ``` **2.2 客户端代码要点说明** **依赖管理:**使用 AsyncExitStack 来管理异步上下文,确保 MCP 会话和传输层在程序退出或异常时能够被正确清理。 **环境变量配置:**通过 dotenv 加载 .env 文件中的配置(如 OPENAI_API_KEY、BASE_URL、MODEL),避免硬编码敏感信息。 **动态工具发现:**在 process_query 中,每次处理请求前都会调用 session.list_tools() 动态获取服务器当前可用的工具列表,并自动转换为 OpenAI Function Calling 所需的 tools 格式。 **工具调用闭环:**当大模型返回 finish_reason == "tool_calls" 时,客户端会自动解析参数,调用 session.call_tool 执行工具,并将结果作为 tool 角色消息追加到上下文中,再次请求大模型生成最终回复。 **入口参数校验:**在 main 函数中校验命令行参数,确保传入了服务器脚本路径,并统一在文件顶部导入 sys 模块,避免运行时错误。 **三、MCP 服务器 Server 与使用** **3.1 MCP 服务器概念介绍** 根据 MCP 协议定义,Server 可以提供三种类型的标准能力。每个 Server 可同时提供这三种能力,或仅提供其中一种: **Resources(资源)**:类似于文件数据读取,可以是本地文件资源,也可以是 API 响应返回的内容。 **Tools(工具)**:第三方服务或功能函数,通过此能力控制 LLM 可调用哪些函数。 **Prompts(提示词)**:为用户预先定义好的、用于完成特定任务的模板。 **3.2 MCP 服务器通讯机制** Model Context Protocol(MCP)旨在将大型语言模型直接连接至数据源,实现无缝集成。根据 MCP 规范,当前支持两种传输方式: |**传输方式**|**通信机制**|**连接特性**|**适用场景**| | --- | --- | --- | --- | |stdio|客户端将服务器程序作为子进程启动,通过标准输入(stdin)和标准输出(stdout)进行数据交换。|本地进程通信|客户端和服务器在同一台机器上运行的场景。| |SSE|客户端和服务器通过 HTTP 协议通信,利用 Server-Sent Events 实现服务器向客户端的实时数据推送。|基于长连接的单向推送|客户端和服务器位于不同物理位置的场景。| **注意:**SSE 仅支持服务器向客户端的单向通信,而 WebSocket 则支持双向通信。HTTP 采用传统的请求-响应模式,每次请求通常建立新的连接;SSE 则基于长连接,适用于实时通知、股票行情更新等场景。 **3.3 服务器创建流程** 创建一个标准的 MCP 服务器通常包含以下步骤: **引入 mcp.server.fastmcp 模块并初始化 FastMCP 实例。** **定义具体的业务逻辑函数(如 API 请求、数据处理等)。** **使用 @mcp.tool() 装饰器将业务函数注册为 MCP 工具,并编写清晰的文档字符串(Docstring)作为工具描述。** **在 __main__ 中调用 mcp.run(transport='stdio') 启动服务器。** **3.4 服务器代码编写** 以下是一个完整的MCP 服务器实现: ```javascript import json import httpx from typing import Any from mcp.server.fastmcp import FastMCP # 初始化 MCP 服务器 mcp = FastMCP("WeatherServer") # OpenWeather API 配置 OPENWEATHER_API_BASE = "https://api.openweathermap.org/data/2.5/weather" API_KEY = "YOUR_API_KEY" # 请替换为你自己的 OpenWeather API Key USER_AGENT = "weather-app/1.0" async def fetch_weather(city: str) -> dict[str, Any] | None: """ 从 OpenWeather API 获取天气信息。 :param city: 城市名称(需使用英文,如 Beijing) :return: 天气数据字典;若出错返回包含 error 信息的字典 """ params = { "q": city, "appid": API_KEY, "units": "metric", "lang": "zh_cn" } headers = {"User-Agent": USER_AGENT} async with httpx.AsyncClient() as client: try: response = await client.get(OPENWEATHER_API_BASE, params=params, headers=headers, timeout=30.0) response.raise_for_status() return response.json() # 返回字典类型 except httpx.HTTPStatusError as e: return {"error": f"HTTP 错误: {e.response.status_code}"} except Exception as e: return {"error": f"请求失败: {str(e)}"} def format_weather(data: dict[str, Any] | str) -> str: """ 将天气数据格式化为易读文本。 :param data: 天气数据(可以是字典或 JSON 字符串) :return: 格式化后的天气信息字符串 """ # 如果传入的是字符串,则先转换为字典 if isinstance(data, str): try: data = json.loads(data) except Exception as e: return f"无法解析天气数据: {e}" # 如果数据中包含错误信息,直接返回错误提示 if "error" in data: return f"⚠️ {data['error']}" # 提取数据时做容错处理 city = data.get("name", "未知") country = data.get("sys", {}).get("country", "未知") temp = data.get("main", {}).get("temp", "N/A") humidity = data.get("main", {}).get("humidity", "N/A") wind_speed = data.get("wind", {}).get("speed", "N/A") # weather 可能为空列表,因此用 [0] 前先提供默认字典 weather_list = data.get("weather", [{}]) description = weather_list[0].get("description", "未知") return ( f"🌍 {city}, {country}\n" f"🌡 温度: {temp}°C\n" f"💧 湿度: {humidity}%\n" f"🌬 风速: {wind_speed} m/s\n" f"🌤 天气: {description}\n" ) @mcp.tool() async def query_weather(city: str) -> str: """ 输入指定城市的英文名称,返回今日天气查询结果。 :param city: 城市名称(需使用英文) :return: 格式化后的天气信息 """ data = await fetch_weather(city) return format_weather(data) if __name__ == "__main__": # 以标准 I/O 方式运行 MCP 服务器 mcp.run(transport='stdio') ``` **3.5 服务器代码要点说明** **FastMCP 封装:**使用 FastMCP 可以极大地简化服务器的创建过程,无需手动处理底层的 JSON-RPC 通信协议。 **工具注册与描述:**@mcp.tool() 装饰器会自动将函数名和参数类型转换为 MCP 工具定义。函数的 Docstring(如“输入指定城市的英文名称…”)至关重要,它会被大模型读取并作为工具的功能说明,直接影响大模型调用工具的准确率。 **异步网络请求:**使用 httpx.AsyncClient 进行异步 HTTP 请求,避免阻塞 MCP 服务器的事件循环,确保高并发下的响应性能。 **健壮的错误处理:**在 fetch_weather 中捕获了 HTTPStatusError 和通用 Exception,并将错误信息封装为字典返回,防止因外部 API 异常导致整个 MCP 服务器崩溃。 **数据格式化:**将原始的 JSON 数据通过 format_weather 转换为人类可读的文本格式,这有助于大模型更好地理解工具返回的结果,从而生成更准确的回复。

优秀的个人博客,低调大师

AI 网关毫秒级语义路由原理与性能实测

在 AI 网关判断一句话该送去哪个模型、要不要拦截时,端到端耗时通常在数十毫秒以内。实测 GPU 路径表明:共享底座模型跑一次前向计算耗时约 **4 毫秒**,而领域判断、越狱检测等 5 个不同维度的分类头,加起来仅多花 **不到 2 毫秒**。 这背后依赖的是一套完整的“向量、BERT 与 LoRA”组合架构。本文结合实际工程逻辑与测试数据,拆解 AI 网关如何实现毫秒级语义识别。 --- # 1. 视频实测与架构演示 如需直观了解性能测算过程、向量空间分类演示及多 LoRA 动态调度流程,请参阅下方视频说明: --- # 2. 向量空间的分类问题:角度与长度 在语义计算中,输入文本首先被转换为高维空间中的向量。判断两句话是否相似存在两种主要度量视角: 1. **长度(欧氏距离)**:容易受句式长短和词频影响。例如一短一长两句同意图请求,欧氏距离可能相差巨大。 1. **角度(余弦相似度)**:关注向量指向的方向。方向代表“文本在说什么”,长度代表“表达的啰嗦程度”。因此语义分类通常优先考虑方向。 ### 向量检索方案的局限 在 Kong AI 网关的配置中,距离度量指标支持自由切换: ``` vectordb: dimensions: 1024 distance_metric: cosine vectordb: dimensions: 1024 distance_metric: euclidean ``` 虽然基于向量数据库的纯检索方案速度极快,但在生产环境下存在明确瓶颈: - **边界骑墙问题**:长尾复杂 Prompt 在向量角度上容易处于分类交界区域,产生误判。 - **预置盲区**:向量检索要求提前预设所有分类向量,面对未预料到的新模式无法准确分类。 --- # 3. LLM 的单向与 BERT 的双向 Transformer 架构主要分为编码器(Encoder)与解码器(Decoder): - **LLM(GPT / DeepSeek)**:基于 Decoder-only 架构,采用自回归的下三角注意力机制(只能向前看,预测后续 Token),专为文本生成设计。 - **BERT**:基于 Encoder-only 架构,采用双向注意力机制(能同时看到前后完整上下文),适合文本分类与语义理解任务。 因此在 AI 网关的语义路由层,采用 Encoder 路线比用 Mini-LLM 更加高效准确。 --- # 4. ModernBert 的架构升级 传统的 BERT 模型在处理当下长 Prompt 时存在两大痛点:序列上限仅 512 Token,且注意力计算效率较低。 2024 年底开源的 **ModernBert** 对此做出了关键改进: 1. **8192 Token 原生上下文**:相比老版 BERT 拓展了 16 倍。 1. **局部与全局注意力交替**:采用滑动窗口局部注意力与全局注意力按层交替堆叠,兼顾了长距离上下文捕捉能力与计算效率。 --- # 5. Multi-LoRA 并行接入机制 为了在单一底座上同时挂载领域识别、越狱检测、PII 脱敏等多个分类任务,全量微调会带来巨大的显存开销。通过低秩自适应(LoRA),可以在冻结原矩阵 W0 的前提下,增加 A 与 B 低秩旁路。 ``` import torch import torch.nn as nn class MultiLoRAGateway(nn.Module): def __init__(self, base_model, lora_heads): super().__init__() self.base_model = base_model self.lora_heads = nn.ModuleDict(lora_heads) def forward(self, input_ids, attention_mask): outputs = self.base_model(input_ids=input_ids, attention_mask=attention_mask) pooled_features = outputs.last_hidden_state[:, 0, :] return {name: head(pooled_features) for name, head in self.lora_heads.items()} ``` ### 参数量与性能实测数据 - **参数优化**:以 ModernBert 隐藏维度 768、Rank 设为 32 为例,直接更新 768x768 矩阵需近 60 万参数;而走 LoRA 旁路 (768x32 + 32x768) 仅需不到 5 万参数。 - **文件体积**:单个分类任务的 LoRA 适配器仅约 735 万参数(约占 3.4 亿底座参数的 2%),文件大小仅 **28MB**。 - **并发优势**:28MB 的轻量体积允许十几个 LoRA 适配器常驻显存,共用 1 次 ModernBert 底座前向计算(~4ms),多头并行计算仅需 ** Gateway["AI 网关路由器"] Gateway --> FastPath["规则引擎 ( Encoder["ModernBert 共享底座 (~4ms)"] Encoder --> Head1["领域意图 LoRA ( Head2["越狱安全 LoRA ( Head3["PII 脱敏 LoRA ( Dispatcher["动态负载均衡器"] Head2 --> Dispatcher Head3 --> Dispatcher Dispatcher --> LLM["后端 LLM 集群"] ``` ### 核心结论 1. 向量空间的角度度量解决了大部分基础问题,但无法覆盖复杂边界与新词盲区。 1. 双向 Encoder 架构适合做语义识别,ModernBert 将处理窗口拓展至 8192 Token。 1. 共享 ModernBert 底座结合多 28MB LoRA 适配器,实现了 4ms 底座计算 + <2ms 多头并行的毫秒级语义路由能力。

优秀的个人博客,低调大师

理解Mysql索引原理及特性 | 京东物流技术团队

作为开发人员,碰到了执行时间较长的sql时,基本上大家都会说”加个索引吧”。但是索引是什么东西,索引有哪些特性,下面和大家简单讨论一下。 1 索引如何工作,是如何加快查询速度 索引就好比书本的目录,提高数据库表数据访问速度的数据库对象。当我们的请求打过来之后,如果有目录,就会快速的定位到章节,再从章节里找到数据。如果没有目录,如大海捞针一般,难度可见一斑。这就是我们经常碰到的罪魁祸首,全表扫描。 一条索引记录中包含的基本信息包括:键值(即你定义索引时指定的所有字段的值)+逻辑指针(指向数据页或者另一索引页)。通常状况下,由于索引记录仅包含索引字段值(以及4-9字节的指针),索引实体比真实的数据行要小许多,索引页相较数据页来说要密集许多。一个索引页可以存储数量更多的索引记录,这意味着在索引中查找时在I/O上占很大的优势,理解这一点有助于从本质上了解使用索引的优势,也是大部分性能优化所需要切入的点。 1)没有索引的情况下访问数据: 2)使用平衡二叉树结构索引的情况下访问数据: 第一张图没有使用索引我们会进行顺序查找,依照数据顺序逐个进行匹配,进行了5次寻址才查询出所需数据,第二张图用了一个简单的平衡二叉树索引之后我们只用了3次,这还是数据量小的情况下,数据量大了效果更明显,所以总结来说创建索引就是为了加快数据查找速度; 2 索引的组成部分和种类 常见的索引的实现方式有很多种,比如hash、数组、树,下面为大家介绍下这几种模型使用上有什么区别 2.1 hash hash思路简单,就是把我们插入的key通过hash函数算法(以前一般是取余数,就好比hashmap的计算方式移位异或之类的),计算出对应的value,把这个value放到一个位置,这个位置叫做哈希槽。对应磁盘位置指针放入hash槽里面。一句话总结hash索引,就是存储了索引字段的hash值和数据所在磁盘文件指针。 但是不可避免的是,无论什么算法,数据量大了之后难免会出现不同的数据被放在一个hash槽里面。比如字典上的 “吴”和”武”就是同音,你查字典的时候到这里只能顺序往下去找了。索引的处理也是这样,会拉出一个链表,需要的时候顺序遍历即可。 缺点:无序索引,区间查询性能低,因为区间查询会造成多次访问磁盘,多次io耗时是很难接受的。 优点:insert迅速,只需往后补就行 场景:等值查询, 比如memcached 。不适用大量重复数据的列,避免hash冲突 总结:想成java的hashmap即可 2.2 有序数组 如果我们需要区间查询的时候,hash索引的性能就不尽如人意了。这个时候有序数组的优势就能体现出来了。 当我们需要从一个有序数组里取A和B之间的值时,只需要通过二分法定位到A的位置,时间复杂度O(log(N)),接着从A遍历到B即可,论速度的话,基本上可以说是最快的了。但是当我们需要更新的时候,需要进行的操作就很多了。如果需要插入一条数据,你需要挪动数据之后的所有数据,浪费性能。所以总结来说,只有不怎么变化的数据适合有序数组结构的索引。 缺点:insert新数据的时候,需要改变后续所有数据,成本略高。 优点:查询速度很快,理论最大值。 场景:归档查询,日志查询等极少变化的 总结:就是顺序排的数组 2.3 二叉搜索树 基本原则是树的左节点都小于父节点,右节点都大于父节点 这里我们就能看出来,二叉搜索树的查询效率原则上是O(log(N)),为了保证是平衡二叉树,更新效率也是O(log(N))。但是数据很多的情况树的高度会达到很高,过多次访问磁盘,是不可取的。并且极端情况下,树退化成链表,查询的复杂度会被拉成O(n)。 进化成多叉树,也就是多个子节点的时候,会大大的减少树的高度,降低访问磁盘。 缺点:数据量大的时候,树会过高,导致多次访问磁盘 优点:进化成多叉树,会降低树高,访问磁盘次数。 场景:适用很多场景 总结:左小右大的树 2.4 B树 在每个节点存储多个元素,在每个节点尽可能多的存储数据。每个节点可以存储1000个索引(16k/16=1000),这样就将二叉树改造成了多叉树,通过增加树的叉树,将树从高瘦变为矮胖。构建1百万条数据,树的高度只需要2层就可以(1000*1000=1百万),也就是说只需要2次磁盘IO就可以查询到数据。磁盘IO次数变少了,查询数据的效率也就提高了。 这种数据结构我们称为B树,B树是一种多叉平衡查找树 2.5 B+树 B+树和B树最主要的区别在于非叶子节点是否存储数据的问题。 B树:非叶子节点和叶子节点都会存储数据。 B+树:只有叶子节点才会存储数据,非叶子节点至存储键值。叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。 正是因为B+树的叶子节点是通过链表连接的,所以找到下限后能很快进行区间查询,比正常的中序遍历快 3 索引的维护 当你insert一条数据的时候,索引需要做出必要的操作来保证数据的有序型。一般自增数据直接在后面加就行了,特殊情况下如果数据加到了中间,就需要挪动后面所有的数据,这样效率比较受影响。 最糟糕的情况,如果当前的数据页(页是mysql存储的最小单位)存满了,需要申请一个新的数据页,这个过程被称为页分裂。如果造成了页分裂的话,势必会造成性能的影响。但是mysql并不是无脑的数据分裂,如果你是从中间进行数据分裂的话,对于自增主键,会导致一半的性能浪费。mysql会根据你的索引的类型,和追踪插入数据的情况决定分裂的方式,一般都存在mysql数据页的head里面,如果是零散的插入,会从中间分裂。如果是顺序插入,一般是会选择插入点开始分裂,或者插入点往后几行导致的。决定是否从中间分裂,还是从最后分裂。 如果插入的是不规则的数据,没法保证后一个值比前一个大,就会触发上面说的分裂逻辑,最后达到下面的效果 所以绝大多数情况下,我们都需要使用自增索引,除非需要业务自定义主键,最好能保证只有一个索引,且索引是唯一索引。这样可以避免回表,导致查询搜索两棵树。保证数据页的有序性,可以更好的使用索引。 4 回表 通俗的讲就是,如果索引的列在 select 所需获得的列中(因为在 mysql 中索引是根据索引列的值进行排序的,所以索引节点中存在该列中的部分值)或者根据一次索引查询就能获得记录就不需要回表,如果 select 所需获得列中有大量的非索引列,索引就需要先找到主键,再到表中找到相应的列的信息,这就叫回表。 要介绍回表自然就得介绍聚集索引和非聚集索引 InnoDB聚集索引的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引: 如果表定义了主键,则PK就是聚集索引; 如果表没有定义主键,则第一个非空唯一索引(not NULL unique)列是聚集索引; 否则,InnoDB会创建一个隐藏的row-id作为聚集索引; 当我们使用普通索引查询方式,则需要先搜索普通索引树,然后得到主键 ID后,再到 ID 索引树搜索一次。因为非主键索引的叶子节点里面,实际存的是主键的ID。这个过程虽然用了索引,但实际上底层进行了两次索引查询,这个过程就称为回表。也就是说,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。或者有高频请求时,合理建立联合索引,防止回表。 5 索引覆盖 一句话表达的话,是只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。落实到sql上的话,只要执行计划里面的输出结果Extra字段为Using index时,能够触发索引覆盖。 常见的优化手段,就是上面提到的,将查询的字段都建到索引里面,至于dba愿不愿意让你建,那就需要你们自己battle了。 一般索引覆盖适用的场景包括 全表count查询优化、列查询回表、分页回表。高版本的mysql已经做了优化,当命中联合索引的其中一个字段,另外一个是id的时候,会自动优化,无需回表。因为二级索引的叶子上存了primary key,也算索引覆盖,无需额外成本。 6 最左匹配原则 简单来说,就是你使用 ‘xx%’的时候,符合条件的话也会使用索引。 如果是联合索引的话,我举个例子,创建一个(a,b)的联合索引 可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。但是我们又可发现a在等值的情况下,b值又是按顺序排列的,但是这种顺序是相对的。这是因为MySQL创建联合索引的规则是首先会对联合索引的最左边第一个字段排序,在第一个字段的排序基础上,然后在对第二个字段进行排序。所以b=2这种查询条件没有办法利用索引。举个例子,我弄一个索引, KEYidx_time_zone(time_zone,time_string) USING BTREE 执行第一条sql,全表扫描 执行第二条sql,可以看到使用了索引。 再看两条sql,建立的索引是 KEYidx_time_zone(time_zone,time_string) USING BTREE 按照正常逻辑来说,第二条sql是不符合索引字段的顺序的,应该不能使用索引才对,但是实际情况却和我们期望的不太一样,这是为啥呢? 从mysql被oracle收购以后,mysql加入了很多oracle以前的技术,高版本mysql自动优化了where条件的先后顺序。简单来说就是查询优化器做了这一步操作,sql会做预处理,那一条能更好的查询就会使用那种规则。 顺便提一下mysql的查询优化器能帮忙干的一些事 6.1 条件转化 例如where a=b and b=2,可以得到a=2,条件传递。最后的sql是 a=2 and b=2 > < = like 都可以传递 6.2 无效代码的排除 例如 where 1=1 and a=2, 1=1永远是正确的,所以最后会优化成 a=2 在比如 where 1=0 永远是false的,这样的也会被排除掉,整sql无效 或者非空字段 where a is null ,这样的也会被排除 6.3 提前计算 包含数学运算的部分,例如 where a= 1+2 会帮你算好,where a=3 6.4 存取类型 当我们评估一个条件表达式,MySQL判断该表达式的存取类型。下面是一些存取类型,按照从最优到最差的顺序进行排列: system系统表,并且是常量表 const 常量表 eq_ref unique/primary索引,并且使用的是’=’进行存取 ref 索引使用’=’进行存取 ref_or_null 索引使用’=’进行存取,并且有可能为NULL range 索引使用BETWEEN、IN、>=、LIKE等进行存取 index 索引全扫描 ALL 表全扫描 经常看执行计划的,一眼就能看出来这是啥意思,举个例子 where index_col=2 and normal_col =3 这里就会选用index_col=2 会作为驱动项。驱动项的意思是指一个sql选定他的执行计划的时候,可能有多条执行路径,一个是全表扫描,再过滤是否符合索引字段及非索引字段的值。另一种是通过索引字段,键值=2找到对应的索引树,过滤后的结果,再比较是否符合非索引字段的值。一般情况下,走索引都比全表扫描需要读取磁盘的次数少,所以称它为更好的执行路径,也就是通过索引字段,作为其驱动表达式 6.5 范围存取 简单来说,a in(1,2,3) 和 a=1 or a=2 or a=3 是一样的,between 1 and 2 和 a>1 and a<2也是一样的, 无需可以优化。 6.6 索引存取类型 避免使用相同前缀的索引,也就是一个字段不要在多个索引上有相同的前缀。比如一个字段已经建立了唯一索引,这个时候如果再给他建立一个联合索引,会导致优化器并不知道你要使用哪个索引。或者你建了前缀相同的一个单索引,一个联合索引,就算你写上了条件,也不一定能用上联合索引。当然,可以force,这就另说了。 6.7 转换 简单的表达式可以进行转换,比如 where -2 = a 会自动变成 where a= -2 ,但是如果牵扯到数学运算,就不能转换了 比如 where 2= -a 这时候不会自动转成 where a =-2. 第二条sql就可以使用索引 所以 我们在开发的过程中,需要注意sql的写法,自觉写成 where a=-2 6.8 and、union、order by、group by等 1)and and条件后,如果都没索引,扫描全表。有一个存取类型更好,见5.4 ,会使用存储类型更好的索引,如果都一样,哪个索引先创建,用哪个。 2)union union 每条语句单独优化 这里就会分别执行两条sql,用到索引,再合并结果集 3)order by order by 会过滤无效排序,比如一个字段本来就有索引 第二条sql和第一条的查询效果是一样的 所以,写sql的时候,不要写无用排序,比如order by ‘xxx’ 这样没有意义。 4)group by 简单来说 group by 的字段,有索引会走索引,group by a order by a 这里的order by等于没写,结果集已经是排序完毕的了,参考 6.8-3 order by select distinct col_a from table a 等价于 select col_a from a group by col_a 7 索引下推 主要的核心点就在于把数据筛选的过程放在了存储引擎层去处理,而不是像之前一样放到Server层去做过滤。 如果在一张表上,name和age都建立索引,查询条件为 where name like ‘xx%’ and age=11,在低版本的mysql(5.6 以下)的根据索引的最左匹配原则,可以得到放弃了age,只根据name过滤数据。根据name拿到所有的id之后,再根据id回表。 高版本mysql里,没有忽略age这个属性,带着age属性过滤,直接过滤掉了age为11的数据,假设不根据age过滤的数据为10条,过滤后只剩3条,就少了7次回表。减少了io会大量减少性能消耗 8 小表驱动大表 小表驱动大表,也是我们听惯了的话了,其含义主要是指小表的数据集驱动大表的数据集,减少连接次数。打个比方: 表A有1w数据,表B有100w数据,如果A表作为驱动表,处于循环的外层,那么只需要1w次的连接即可。如果B表在外层,那么则需要循环100w次。 下面我们实际测试看看,准备环境mysql 5.7+ 准备两张表,一张表 ib_asn_d 数据 9175, 一张表 bs_itembase_ext_attr 数据 1584115,都在商品编码字段上有索引。 首先小表驱动大表 多次反复测试,执行时间大概7秒。 接下来看看大表驱动小表。 将近300秒,不是一个量级的。 接下来分别分析执行计划,执行计划里第一条就是驱动表。 小表驱动大表,大表用了索引,小表全表扫描,只扫描8000多行 大表驱动小表,大表全表扫描,需要扫描147w行。 经过多次测试得出了结论: 当使用left join时,左表是驱动表,右表是被驱动表 ; 当使用right join时,右表是驱动表,左表是被驱动表 ; 当使用inner join时,mysql会选择数据量比较小的表作为驱动表,大表作为被驱动表 ; 驱动表索引不生效,非驱动表索引生效 保证小表是驱动表很重要。 9 总结 覆盖索引:如果查询条件使用的是普通索引(或是联合索引的最左原则字段),查询结果是联合索引的字段或是主键,不用回表操作,直接返回结果,减少IO磁盘读写读取整行数据,所以高频字段建立联合索引是很有必要的 最左前缀:联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。建立索引的时候,注意左前缀不要重复,避免查询优化器无法判定如何使用索引 索引下推:name like ‘hello%’and age >10 检索,MySQL 5.6版本之前,会对匹配的数据进行回表查询。5.6版本后,会先过滤掉age<10的数据,再进行回表查询,减少回表率,提升检索速度 作者:京东物流吴思维 来源:京东云开发者社区 转载请注明来源

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册