当前位置: 首页 > news >正文

玩转Mysql系列 - 第20篇:异常捕获及处理详解

这是Mysql系列第20篇。

环境:mysql5.7.25,cmd命令中进行演示。

代码中被[]包含的表示可选,|符号分开的表示可选其一。

需求背景

我们在写存储过程的时候,可能会出现下列一些情况:

  1. 插入的数据违反唯一约束,导致插入失败

  2. 插入或者更新数据超过字段最大长度,导致操作失败

  3. update影响行数和期望结果不一致

遇到上面各种异常情况的时,可能需要我们能够捕获,然后可能需要回滚当前事务。

本文主要围绕异常处理这块做详细的介绍。

此时我们需要使用游标,通过游标的方式来遍历select查询的结果集,然后对每行数据进行处理。

本篇内容

  • 异常分类详解

  • 内部异常详解

  • 外部异常详解

  • 掌握乐观锁解决并发修改数据出错的问题

  • update影响行数和期望结果不一致时的处理

准备数据

创建库:javacode2018

创建表:test1,test1表中的a字段为主键。

/*建库javacode2018*/
drop database if exists javacode2018;
create database javacode2018;/*切换到javacode2018库*/
use javacode2018;DROP TABLE IF EXISTS test1;
CREATE TABLE test1(a int PRIMARY KEY);

异常分类

我们将异常分为mysql内部异常和外部异常

mysql内部异常

当我们执行一些sql的时候,可能违反了mysql的一些约束,导致mysql内部报错,如插入数据违反唯一约束,更新数据超时等,此时异常是由mysql内部抛出的,我们将这些由mysql抛出的异常统称为内部异常。

外部异常

当我们执行一个update的时候,可能我们期望影响1行,但是实际上影响的不是1行数据,这种情况:sql的执行结果和期望的结果不一致,这种情况也我们也把他作为外部异常处理,我们将sql执行结果和期望结果不一致的情况统称为外部异常。

Mysql内部异常

示例1

test1表中的a字段为主键,我们向test1表同时插入2条数据,并且放在一个事务中执行,最终要么都插入成功,要么都失败。

创建存储过程:
/*删除存储过程*/
DROP PROCEDURE IF EXISTS proc1;
/*声明结束符为$*/
DELIMITER $
/*创建存储过程*/
CREATE PROCEDURE proc1(a1 int,a2 int)BEGINSTART TRANSACTION;INSERT INTO test1(a) VALUES (a1);INSERT INTO test1(a) VALUES (a2);COMMIT;END $
/*结束符置为;*/
DELIMITER ;

上面存储过程插入了两条数据,a的值都是1。

验证结果:
mysql> DELETE FROM test1;
Query OK, 0 rows affected (0.00 sec)mysql> CALL proc1(1,1);
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
mysql> SELECT * from test1;
+---+
| a |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

上面先删除了test1表中的数据,然后调用存储过程proc1,由于test1表中的a字段是主键,插入第二条数据时违反了a字段的主键约束,mysql内部抛出了异常,导致第二条数据插入失败,最终只有第一条数据插入成功了。

上面的结果和我们期望的不一致,我们希望要么都插入成功,要么失败。

那我们怎么做呢?我们需要捕获上面的主键约束异常,然后发现有异常的时候执行rollback回滚操作,改进上面的代码,看下面示例2。

示例2

我们对上面示例进行改进,捕获上面主键约束异常,然后进行回滚处理,如下:

创建存储过程:
/*删除存储过程*/
DROP PROCEDURE IF EXISTS proc2;
/*声明结束符为$*/
DELIMITER $
/*创建存储过程*/
CREATE PROCEDURE proc2(a1 int,a2 int)BEGIN/*声明一个变量,标识是否有sql异常*/DECLARE hasSqlError int DEFAULT FALSE;/*在执行过程中出任何异常设置hasSqlError为TRUE*/DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET hasSqlError=TRUE;/*开启事务*/START TRANSACTION;INSERT INTO test1(a) VALUES (a1);INSERT INTO test1(a) VALUES (a2);/*根据hasSqlError判断是否有异常,做回滚和提交操作*/IF hasSqlError THENROLLBACK;ELSECOMMIT;END IF;END $
/*结束符置为;*/
DELIMITER ;
上面重点是这句:
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET hasSqlError=TRUE;

当有sql异常的时候,会将变量hasSqlError的值置为TRUE

模拟异常情况:
mysql> DELETE FROM test1;
Query OK, 2 rows affected (0.00 sec)mysql> CALL proc2(1,1);
Query OK, 0 rows affected (0.00 sec)mysql> SELECT * from test1;
Empty set (0.00 sec)

上面插入了2条一样的数据,插入失败,可以看到上面test1表无数据,和期望结果一致,插入被回滚了。

模拟正常情况:
mysql> DELETE FROM test1;
Query OK, 0 rows affected (0.00 sec)mysql> CALL proc2(1,2);
Query OK, 0 rows affected (0.00 sec)mysql> SELECT * from test1;
+---+
| a |
+---+
| 1 |
| 2 |
+---+
2 rows in set (0.00 sec)

上面插入了2条不同的数据,最终插入成功。

外部异常

外部异常不是由mysql内部抛出的错误,而是由于sql的执行结果和我们期望的结果不一致的时候,我们需要对这种情况做一些处理,如回滚操作。

示例1

我们来模拟电商中下单操作,按照上面的步骤来更新账户余额。

电商中有个账户表和订单表,如下:
DROP TABLE IF EXISTS t_funds;
CREATE TABLE t_funds(user_id INT PRIMARY KEY COMMENT '用户id',available DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '账户余额'
) COMMENT '用户账户表';
DROP TABLE IF EXISTS t_order;
CREATE TABLE t_order(id int PRIMARY KEY AUTO_INCREMENT COMMENT '订单id',price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '订单金额'
) COMMENT '订单表';
delete from t_funds;
/*插入一条数据,用户id为1001,余额为1000*/
INSERT INTO t_funds (user_id,available) VALUES (1001,1000);
下单操作涉及到操作上面的账户表,我们用存储过程来模拟实现:
/*删除存储过程*/
DROP PROCEDURE IF EXISTS proc3;
/*声明结束符为$*/
DELIMITER $
/*创建存储过程*/
CREATE PROCEDURE proc3(v_user_id int,v_price decimal(10,2),OUT v_msg varchar(64))a:BEGINDECLARE v_available DECIMAL(10,2);/*1.查询余额,判断余额是否够*/select a.available into v_available from t_funds a where a.user_id = v_user_id;if v_available<=v_price THENSET v_msg='账户余额不足!';/*退出*/LEAVE a;END IF;/*模拟耗时5秒*/SELECT sleep(5);/*2.余额减去price*/SET v_available = v_available - v_price;/*3.更新余额*/START TRANSACTION;UPDATE t_funds SET available = v_available WHERE user_id = v_user_id;/*插入订单明细*/INSERT INTO t_order (price) VALUES (v_price);/*提交事务*/COMMIT;SET v_msg='下单成功!';END $
/*结束符置为;*/
DELIMITER ;

上面过程主要分为3步骤:验证余额、修改余额变量、更新余额。

开启2个cmd窗口,连接mysql,同时执行下面操作:
USE javacode2018;
CALL proc3(1001,100,@v_msg);
select @v_msg;
然后执行:
mysql> SELECT * FROM t_funds;
+---------+-----------+
| user_id | available |
+---------+-----------+
|    1001 |    900.00 |
+---------+-----------+
1 row in set (0.00 sec)mysql> SELECT * FROM t_order;
+----+--------+
| id | price  |
+----+--------+
|  1 | 100.00 |
|  2 | 100.00 |
+----+--------+
2 rows in set (0.00 sec)

上面出现了非常严重的错误:下单成功了2次,但是账户只扣了100。

上面过程是由于2个操作并发导致的,2个窗口同时执行第一步的时候看到了一样的数据(看到的余额都是1000),然后继续向下执行,最终导致结果出问题了。

上面操作我们可以使用乐观锁来优化。

乐观锁的过程:用期望的值和目标值进行比较,如果相同,则更新目标值,否则什么也不做。

乐观锁类似于java中的cas操作,这块需要了解的可以点击:详解CAS

我们可以在资金表t_funds添加一个version字段,表示版本号,每次更新数据的时候+1,更新数据的时候将version作为条件去执行update,根据update影响行数来判断执行是否成功,优化上面的代码,见示例2

示例2

对示例1进行优化。

创建表:
DROP TABLE IF EXISTS t_funds;
CREATE TABLE t_funds(user_id INT PRIMARY KEY COMMENT '用户id',available DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '账户余额',version INT DEFAULT 0 COMMENT '版本号,每次更新+1'
) COMMENT '用户账户表';DROP TABLE IF EXISTS t_order;
CREATE TABLE t_order(id int PRIMARY KEY AUTO_INCREMENT COMMENT '订单id',price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '订单金额'
)COMMENT '订单表';
delete from t_funds;
/*插入一条数据,用户id为1001,余额为1000*/
INSERT INTO t_funds (user_id,available) VALUES (1001,1000);
创建存储过程:
/*删除存储过程*/
DROP PROCEDURE IF EXISTS proc4;
/*声明结束符为$*/
DELIMITER $
/*创建存储过程*/
CREATE PROCEDURE proc4(v_user_id int,v_price decimal(10,2),OUT v_msg varchar(64))a:BEGIN/*保存当前余额*/DECLARE v_available DECIMAL(10,2);/*保存版本号*/DECLARE v_version INT DEFAULT 0;/*保存影响的行数*/DECLARE v_update_count INT DEFAULT 0;/*1.查询余额,判断余额是否够*/select a.available,a.version into v_available,v_version from t_funds a where a.user_id = v_user_id;if v_available<=v_price THENSET v_msg='账户余额不足!';/*退出*/LEAVE a;END IF;/*模拟耗时5秒*/SELECT sleep(5);/*2.余额减去price*/SET v_available = v_available - v_price;/*3.更新余额*/START TRANSACTION;UPDATE t_funds SET available = v_available WHERE user_id = v_user_id AND version = v_version;/*获取上面update影响行数*/select ROW_COUNT() INTO v_update_count;IF v_update_count=1 THEN/*插入订单明细*/INSERT INTO t_order (price) VALUES (v_price);SET v_msg='下单成功!';/*提交事务*/COMMIT;ELSESET v_msg='下单失败,请重试!';/*回滚事务*/ROLLBACK;END IF;END $
/*结束符置为;*/
DELIMITER ;

ROW_COUNT()可以获取更新或插入后获取受影响行数。将受影响行数放在v_update_count中。

然后根据v_update_count是否等于1判断更新是否成功,如果成功则记录订单信息并提交事务,否则回滚事务。

验证结果:开启2个cmd窗口,连接mysql,执行下面操作:
use javacode2018;
CALL proc4(1001,100,@v_msg);
select @v_msg;
窗口1结果:
mysql> CALL proc4(1001,100,@v_msg);
+----------+
| sleep(5) |
+----------+
|        0 |
+----------+
1 row in set (5.00 sec)Query OK, 0 rows affected (5.00 sec)mysql> select @v_msg;
+---------------+
| @v_msg        |
+---------------+
| 下单成功!     |
+---------------+
1 row in set (0.00 sec)
窗口2结果:
mysql> CALL proc4(1001,100,@v_msg);
+----------+
| sleep(5) |
+----------+
|        0 |
+----------+
1 row in set (5.00 sec)Query OK, 0 rows affected (5.01 sec)mysql> select @v_msg;
+-------------------------+
| @v_msg                  |
+-------------------------+
| 下单失败,请重试!        |
+-------------------------+
1 row in set (0.00 sec)

可以看到第一个窗口下单成功了,窗口2下单失败了。

再看一下2个表的数据:

mysql> SELECT * FROM t_funds;
+---------+-----------+---------+
| user_id | available | version |
+---------+-----------+---------+
|    1001 |    900.00 |       0 |
+---------+-----------+---------+
1 row in set (0.00 sec)mysql> SELECT * FROM t_order;
+----+--------+
| id | price  |
+----+--------+
|  1 | 100.00 |
+----+--------+
1 row in set (0.00 sec)

也正常。

总结

  1. 异常分为Mysql内部异常和外部异常

  2. 内部异常由mysql内部触发,外部异常是sql的执行结果和期望结果不一致导致的错误

  3. sql内部异常捕获方式

    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET hasSqlError=TRUE;
    
  4. ROW_COUNT()可以获取mysql中insert或者update影响的行数

  5. 掌握使用乐观锁(添加版本号)来解决并发修改数据可能出错的问题

  6. begin end前面可以加标签,LEAVE 标签可以退出对应的begin end,可以使用这个来实现return的效果

相关文章:

玩转Mysql系列 - 第20篇:异常捕获及处理详解

这是Mysql系列第20篇。 环境&#xff1a;mysql5.7.25&#xff0c;cmd命令中进行演示。 代码中被[]包含的表示可选&#xff0c;|符号分开的表示可选其一。 需求背景 我们在写存储过程的时候&#xff0c;可能会出现下列一些情况&#xff1a; 插入的数据违反唯一约束&#xff…...

一些工具类

1、字符串处理工具类 1.1、StrUtils package com.study.java8.util;/*** Classname&#xff1a;StrUtils* Description&#xff1a;字符串工具类* Date&#xff1a;2023/9/9 9:37* Author&#xff1a;jsz15*/import org.apache.commons.lang.text.StrBuilder; import org.apa…...

20230916后台面经整理

1.面对抢优惠券这样的高负载场景&#xff0c;你从架构、负载均衡等方面说一下你的设计&#xff1f; 答了参考Nginx进行负载均衡&#xff0c;然后在每台服务器怎么怎么弄&#xff08;架构每一层怎么设计&#xff09; 参考https://toutiao.io/posts/6z3uu2m/preview&#xff0c;h…...

如何通过快解析测试接口内外网?本地内网ip让外网访问连接

接口调试测试是网络技术员经常工作内容之一。如在公司内部api项目webserver测试&#xff0c;在公司内办公室个人电脑是正常用内网IP访问连接测试的&#xff0c;但在外网电脑需要远程测试时需要怎么测试呢&#xff1f;这里提供一种内网地址让外网访问的通用方法&#xff1a;快解…...

用c++实现五子棋小游戏

五子棋是一款经典小游戏&#xff0c;今天我们就用c实现简单的五子棋小游戏 目录 用到的算法&#xff1a; 思路分析 定义变量 开始写代码 完整代码 结果图&#xff1a; 用到的算法&#xff1a; 合法移动的判断&#xff1a;isValidMove 函数通过检查指定位置是否在棋盘范…...

Android 12.0 SystemUI下拉状态栏定制化之隐藏下拉通知栏布局功能实现(二)

1.前言 在12.0的系统定制化开发中,由于从12.0开始SystemUI下拉状态栏和11.0的变化比较大,所以可以说需要从新分析相关的SystemUI的 布局,然后做分析来实现不同的功能,今天就开始实现关于隐藏SystemUI下拉状态栏中的通知栏布局系列二,去掉下拉状态栏中 通知栏部分 白色的…...

通过finalshell快速在ubuntu上安装jdk1.8

这篇文章主要介绍一下怎么通过finalshell连接ubuntu&#xff0c;然后在ubuntu上安装jdk1.8&#xff0c;让不熟悉linux操作系统的童鞋也能快速地完成安装。 目录 一、准备一台虚拟机 二、安装finalshell远程连接工具 三、获取ubuntu虚拟机的ip地址 四、通过finalshell连接u…...

【Linux从入门到精通】多线程 | 线程互斥(互斥锁)

上篇文章我们对线程 | 线程介绍&线程控制介绍后&#xff0c;本篇文章将会对多线程中的线程互斥与互斥锁的概念进行详解。同时结合实际例子解释了可重入与不被重入函数、临界资源与临界区和原子性的概念。希望本篇文章会对你有所帮助。 文章目录 引入 一、重入与临界 1、1 可…...

Echarts 散点图的详细配置过程

文章目录 散点图 简介配置步骤简易示例 散点图 简介 Echarts散点图是一种常用的数据可视化图表类型&#xff0c;用于展示两个或多个维度的数据分布情况。散点图通过在坐标系中绘制数据点的位置来表示数据的关系。 Echarts散点图的特点如下&#xff1a; 二维数据展示&#xff…...

Nginx详解 五:反向代理

文章目录 1. 正向代理和反向代理1.1 正向代理概述1.1.1 什么是正向代理1.1.2 正向代理的作用1.1.3 正向代理的基本格式 1.2 反向代理概述1.2.1 什么是反向代理1.2.2 反向代理可实现的功能1.2.3 反向代理的可用模块 2. 配置反向代理2.1 反向代理配置参数2.1.1 proxy_pass2.1.2 其…...

【PDF密码】PDF文件打开之后不能打印,怎么解决?

正常的PDF文件是可以打印的&#xff0c;如果PDF文件打开之后发现文件不能打印&#xff0c;我们需要先查看一下自己的打印机是否能够正常运行&#xff0c;如果打印机是正常的&#xff0c;我们再查看一下&#xff0c;文件中的打印功能按钮是否是灰色的状态。 如果PDF中的大多数功…...

深入解析 qsort 函数(下),用冒泡排序模拟实现 qsort 函数

前言&#xff1a;对于库函数有适当了解的朋友们&#xff0c;对于 qsort 函数想必是有认知的&#xff0c;因为他可以对任意数据类型进行排序的功能属实是有点厉害的&#xff0c;本次分享&#xff0c;笔者就给大家带来 qsort 函数的全面的解读 本次知识的分享笔者分为上下俩卷文章…...

Azure + React + ASP.NET Core 项目笔记一:项目环境搭建(二)

有意义的标题 pnpm 安装umi4 脚手架搭建打包语句变更Visual Studio调试Azure 设置变更发布 pnpm 安装 参考官网&#xff0c;或者直接使用npm安装 npm install -g pnpmumi4 脚手架搭建 我这里用的umi4&#xff0c;官网已附上 这里需要把clientapp清空&#xff0c;之后 cd Cl…...

Vmware通过VMware tools设置共享文件夹

步骤说明&#xff1a; 先安装VMware tools&#xff0c;再设置共享文件夹即可。 写在前面&#xff1a; 刚安装虚拟机时&#xff0c;窗口可能显得太小&#xff0c;这是窗口分辨率没有调整导致的。 点击设置->显示->分辨率调整即可 一、安装VMware tools 1.1 点击虚拟机…...

RPA机器人流程自动化专题培训大纲 (针对大学生的版本)

一、课程简介 RPA机器人流程自动化是一种新兴的技术&#xff0c;它通过软件机器人模拟人类操作计算机完成重复性任务&#xff0c;从而实现业务流程的自动化。本课程旨在介绍RPA机器人流程自动化的基本概念、原理和应用&#xff0c;并通过实践案例演示如何应用RPA机器人流程自动…...

数据在内存中的存储——练习4

题目&#xff1a; int main() {char a[1000];int i;for(i0; i<1000; i){a[i] -1-i;}printf("%d",strlen(a));return 0; }思路分析&#xff1a; 已知条件&#xff1a; 通过循环遍历&#xff0c;我们得到的结果是 -1、-2、-3、-4等等。这些是数组内部的存储的元…...

Python 06 之面向对象基础

&#x1f600;前言 在日常编程和软件开发中&#xff0c;我们通常会遇到各种各样的问题&#xff0c;其中很多问题都可以通过面向对象的程序设计方法来解决。面向对象编程不仅可以使代码更加组织化和系统化&#xff0c;而且还可以提高代码的重用性和可维护性。 . 在本教程中&…...

去除pdf/word的水印艺术字

对于pdf中的水印如果无法去除水印&#xff0c;则先另存为word&#xff0c;然后再按下面办法处理即可&#xff1a; 查看宏&#xff0c;创建&#xff1a;删除艺术字 添加内容&#xff1a; Sub 删除艺术字()Dim sh As ShapeFor Each sh In ActiveDocument.ShapesIf sh.Type msoT…...

【Linux】使用 Alist 实现阿里云盘4K播放

一、安装 Alist 官方文档 默认安装在 /opt/alist 中 curl -fsSL "https://alist.nn.ci/v3.sh" | bash -s install自定义安装路径&#xff0c;将安装路径作为第二个参数添加&#xff0c;必须是绝对路径&#xff0c;如果路径以 alist 结尾&#xff0c;则直接安装到给定…...

Gof23设计模式之状态模式

1.概述 【例】通过按钮来控制一个电梯的状态&#xff0c;一个电梯有开门状态&#xff0c;关门状态&#xff0c;停止状态&#xff0c;运行状态。每一种状态改变&#xff0c;都有可能要根据其他状态来更新处理。例如&#xff0c;如果电梯门现在处于运行时状态&#xff0c;就不能…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...