order排序方式研究

news2024/11/27 21:01:55

请直接看原文:
链接:https://juejin.cn/post/7258182427306197051

---------------------------------------------------------------------------------------------------------------------------------

一.前言

在MySQL世界中,排序是一个常见而重要的操作。但你是否了解MySQL内部排序的神奇算法?本文将带你深入了解order by语句的几种算法流程,重点详解全字段排序和rowid排序,并对它们的适用场景进行对比分析。

首先来看一下这张思维导图,对本文内容有个直观的认识。

filesort可以使用的内存空间大小为参数sort_buffer_size的值,默认为2M

img

 

二.正文

假设有如下表结构:

CREATE TABLE t (
  id int(11) NOT NULL,
  city varchar(16) NOT NULL,
  name varchar(16) NOT NULL,
  age int(11) NOT NULL,
  addr varchar(128) DEFAULT NULL,
  PRIMARY KEY (id),
  KEY city (city)
) ENGINE=InnoDB;

使用存储过程来初始化数据:

DELIMITER ;;
CREATE PROCEDURE init_t()
BEGIN
  DECLARE i INT DEFAULT 0;
  DECLARE j INT DEFAULT 0;
        DECLARE k INT DEFAULT 0;
  DECLARE city_list VARCHAR(200) DEFAULT '杭州,上海,武汉,北京';
  DECLARE name_len INT DEFAULT 0;
  DECLARE city VARCHAR(16);
  DECLARE namef VARCHAR(16);
  DECLARE age INT;
  DECLARE addr VARCHAR(128);
  
  SET name_len = LENGTH('abcdefghijklmnopqrstuvwxyz');
  
  -- 第一层循环:按照city遍历
  loop_city: LOOP
    SET i = i + 1;
    SET city = SUBSTRING_INDEX(SUBSTRING_INDEX(city_list, ',', i), ',', -1); -- 获取城市
    
    -- 第二层循环:生成数据
    while(k<=4000) do
      SET j = j + 1;
      SET namef = CONCAT(
        SUBSTR('abcdefghijklmnopqrstuvwxyz', FLOOR(RAND() * name_len) + 1, 1),
        SUBSTR('abcdefghijklmnopqrstuvwxyz', FLOOR(RAND() * name_len) + 1, 1),
        SUBSTR('abcdefghijklmnopqrstuvwxyz', FLOOR(RAND() * name_len) + 1, 1)
      ); -- 随机生成姓名
      
      SET age = FLOOR(RAND() * 60) + 18; -- 随机生成年龄
      
      SET addr = CONCAT(
        CASE FLOOR(RAND() * 5)
        WHEN 0 THEN '杭州市某某区某某街道'
        WHEN 1 THEN '上海市某某区某某街道'
        WHEN 2 THEN '武汉市某某区某某街道'
        ELSE '北京市某某区某某街道' END,
        city,
        CASE FLOOR(RAND() * 5)
        WHEN 0 THEN '西路'
        WHEN 1 THEN '东路'
        WHEN 2 THEN '南路'
        ELSE '北路' END,
        FLOOR(RAND() * 100) + 1,
        '号'
      ); -- 随机生成地址
      
      -- 插入数据
      INSERT INTO t VALUES(j, city, namef, age, addr);
      SET k = k + 1;
                end while;
    SET k = 0;
    
    IF i = 4 THEN -- 第一层循环结束
      LEAVE loop_city;
    END IF;
  END LOOP loop_city;
END ;;
DELIMITER ;

执行如下 SQL 语句:

select city,name,age from t where city='杭州' order by name limit 1000;

 

三.全字段排序

为避免全表扫描,在 city 字段上创建索引之后,我们用 explain 命令来看看这个语句的执行情况。

img

Extra 这个字段中的“Using filesort”表示 MySQL 需要进行额外的排序操作,MySQL 会给每个线程分配一块内存用于排序,称为 sort_buffer

查询示意图如下:

img

该语句执行流程如下:

  1. 初始化 sort_buffer,确定放入 name、city、age 这三个字段;
  2. 从索引 city 找到第一个满足 city='杭州’条件的主键 id,也就是图中的 ID_X;
  3. 到主键 id 索引取出整行,取 name、city、age 三个字段的值,存入 sort_buffer 中;
  4. 从索引 city 取下一个记录的主键 id;
  5. 重复步骤 3、4 直到 city 的值不满足查询条件为止,对应的主键 id 也就是图中的 ID_Y;
  6. 对 sort_buffer 中的数据按照字段 name 做快速排序;
  7. 按照排序结果取前 1000 行返回给客户端。

图中“按 name 排序”这个动作,可能在内存中完成,也可能需要使用外部排序,这取决于排序所需的内存和参数 sort_buffer_size。

sort_buffer_size,就是 MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。

sort_buffer_size 最小值为:32768,32KB,不能设置为0,默认值为 262144,256KB。所以,一般情况下都会容下 rowid+其他排序字段的。

接下来我们演示如何判断排序语句是否使用了临时文件。

如何确定一个排序语句是否使用了临时文件
/* 打开optimizer_trace,只对本线程有效 */
SET optimizer_trace='enabled=on';

/* @a保存Innodb_rows_read的初始值 */
select VARIABLE_VALUE into @a from  performance_schema.session_status where variable_name = 'Innodb_rows_read';

/* 执行语句 */
/* 查看 OPTIMIZER_TRACE 输出 */
select city, name,age from t where city='杭州' order by name limit 1000;
SELECT * FROM information_schema.OPTIMIZER_TRACE\G

/* @b保存Innodb_rows_read的当前值 */
select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = 'Innodb_rows_read';

/* 计算Innodb_rows_read差值 */
select @b-@a;

这个方法是通过查看 OPTIMIZER_TRACE 的结果来确认的,你可以从 number_of_tmp_files 中看到是否使用了临时文件。

img

可以看到此时的 number_of_tmp_files 值为0,说明sort_buffer_size 超过了需要排序的数据量的大小,number_of_tmp_files 就是 0,表示排序可以直接在内存中完成。

为了验证临时文件的使用,我们修改 sort_buffer_size 大小,仅在当前会话生效。

SET sort_buffer_size = 4 * 1024;

全字段排序的 OPTIMIZER_TRACE 部分输出

img

number_of_tmp_files 表示的是,排序过程中使用的临时文件数。内存放不下时,就需要使用外部排序,外部排序一般使用归并排序算法。可以这么简单理解,MySQL 将需要排序的数据分成 8 份,每一份单独排序后存在这些临时文件中。然后把这 8 个有序文件再合并成一个有序的大文件。

上图中的其它参数含义:

  • 我们的示例表中有 4001 条满足 city='杭州’的记录,所以你可以看到 examined_rows=4001,表示参与排序的行数是 4001 行。
  • sort_mode 里面的 packed_additional_fields 的意思是,排序过程对字符串做了“紧凑”处理。即使 name 字段的定义是 varchar(16),在排序过程中还是要按照实际长度来分配空间的。

最后执行select @b-@a,结果为:

img

需要注意的是,上述 select @b-@a 的结果显示为 4002,原因如下:

因为查询 OPTIMIZER_TRACE 这个表时,需要用到临时表,而 internal_tmp_disk_storage_engine 的默认值是 InnoDB。如果使用的是 InnoDB 引擎的话,把数据从临时表取出来的时候,会让 Innodb_rows_read 的值加 1。 所以需要把 internal_tmp_disk_storage_engine 设置成 MyISAM。否则,select @b-@a 的结果会显示为 4002。

 

四.rowid 排序

全字段排序的缺点是如果查询的字段过多,sort_buffer 里面要放的字段数太多,这样内存里能够同时放下的行数很少,要分成很多个临时文件,排序的性能会很差。

我们来看看 MySQL 是如何处理的?max_length_for_sort_data,是 MySQL 中专门控制用于排序的行数据的长度的一个参数。它的意思是,如果单行的长度超过这个值,MySQL 就认为单行太大,要换一个算法。

city、name、age 这三个字段的定义总长度是 36,那么只需要设置将 max_length_for_sort_data 设置的比 36小一点。

SET max_length_for_sort_data = 16;
-- 更改sort_buffer的大小
SET sort_buffer_size = 4 * 1024;

新的算法放入 sort_buffer 的字段,只有要排序的列(即 name 字段)和主键 id。

如下图所示:

img

详细执行步骤如下:

  1. 初始化 sort_buffer,确定放入两个字段,即 name 和 id;
  2. 从索引 city 找到第一个满足 city='杭州’条件的主键 id,也就是图中的 ID_X;
  3. 到主键 id 索引取出整行,取 name、id 这两个字段,存入 sort_buffer 中;
  4. 从索引 city 取下一个记录的主键 id;
  5. 重复步骤 3、4 直到不满足 city='杭州’条件为止,也就是图中的 ID_Y;
  6. 对 sort_buffer 中的数据按照字段 name 进行排序;
  7. 遍历排序结果,取前 1000 行,并按照 id 的值回到原表中取出 city、name 和 age 三个字段返回给客户端。

该排序算法,称之为 rowid 排序。与全字段排序相比,rowid 排序多访问了一次表 t 的主键索引。

需要说明的是,最后的“结果集”是一个逻辑概念,实际上 MySQL 服务端从排序后的 sort_buffer 中依次取出 id,然后到原表查到 city、name 和 age 这三个字段的结果,不需要在服务端再耗费内存存储结果,是直接返回给客户端的。

你可以想一下,这个时候执行 select @b-@a,结果会是多少呢?

img

rowid 排序的 OPTIMIZER_TRACE 部分输出

img

首先,图中的 examined_rows 的值还是 4001,表示用于排序的数据是 4001 行。但是 select @b-@a 这个语句的值变成 5002 了。

因为这时候除了排序过程外,在排序完成后,还要根据 id 去原表取值。由于语句是 limit 1000,因此会多读 1000 行。

从 OPTIMIZER_TRACE 的结果中,你还能看到另外两个信息也变了。

  • sort_mode 变成了 sort_key, rowid,表示参与排序的只有 name 和 id 这两个字段。
  • number_of_tmp_files 变成 6 了,是因为这时候参与排序的行数虽然仍然是 4001 行,但是每一行都变小了,因此需要排序的总数据量就变小了,需要的临时文件也相应地变少了。

 

五.全字段排序 VS rowid 排序

如果 MySQL 实在是担心排序内存太小,会影响排序效率,才会采用 rowid 排序算法,这样排序过程中一次可以排序更多行,但是需要回表查询。

如果 MySQL 认为内存足够大,会优先选择全字段排序,把需要的字段都放到 sort_buffer 中,这样排序后就会直接从内存里面返回查询结果了,不用回表查询。

不管是全字段排序还是 rowid排序,如果仅发生在内存 sort_buffer 中,采用的是快排算法;如果依赖磁盘临时文件,采用的是归并排序。

这也就体现了 MySQL 的一个设计思想:如果内存够,就要多利用内存,尽量减少磁盘访问

对于 InnoDB 表来说,rowid 排序会要求回表多造成磁盘读,因此不会被优先选择;执行全字段排序会减少磁盘访问,因此会被优先选择

 

六.字段索引排序

当然,并不是所有的 order by 语句,都需要排序操作的。从上面分析的执行过程,我们可以看到,MySQL 之所以需要生成临时表,并且在临时表上做排序操作,其原因是原来的数据都是无序的。

如果我们给 name 字段创建索引,效果会变成什么样子:

alter table t add index idx_name(name);

explain select city,name,age from t where city='杭州' order by name limit 1000;

img

可以看到 Extra 中仍然存在 Using filesort,name 字段创建索引后不就是有序的吗?

原因在于:在查询时只会遍历 city 索引对数据进行过滤,不会用到 name 列索引,将符合条件的数据返回到server层,在server对数据通过快排算法进行排序,Extra列会出现 filesort;应该利用索引的有序性,在city和name列建立联合索引,这样根据city过滤后的数据就是按照name排好序的(即直接利用索引的顺序),避免在server层排序。

所以仅靠给 name 创建索引是没法保证数据有序的。

如果我们建立一个 city 和 name 的联合索引,对应的 SQL 语句是:

alter table t add index city_user(city, name);

select city,name,age from t where city='杭州' order by name limit 1000;

查询流程变为这样:

  1. 从索引 (city,name) 找到第一个满足 city='杭州’条件的主键 id;
  2. 到主键 id 索引取出整行,取 name、city、age 三个字段的值,作为结果集的一部分直接返回;
  3. 从索引 (city,name) 取下一个记录主键 id;
  4. 重复步骤 2、3,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。

img

原来的查询语句不需要临时表,也不需要排序。接下来,我们用 explain 的结果来印证一下。

img

从图中可以看到,Extra 字段中没有 Using filesort 了,也就是不需要排序了。而且由于 (city,name) 这个联合索引本身有序,所以这个查询也不用把 4001 行全都读一遍,只要找到满足条件的前 1000 条记录就可以退出了。也就是说,在我们这个例子里,只需要扫描 1000 次。

虽然使用了联合索引(city,name) 可以简化排序,但是由于需要根据主键 id 查询 age 数据(回表查询),所以还有优化的余地。

针对这个查询,我们可以创建一个 city、name 和 age 的联合索引,对应的 SQL 语句就是:

alter table t add index city_user_age(city, name, age);

select city,name,age from t where city='杭州' order by name limit 1000;

整个查询语句的执行流程就变成了:

  1. 从索引 (city,name,age) 找到第一个满足 city='杭州’条件的记录,取出其中的 city、name 和 age 这三个字段的值,作为结果集的一部分直接返回;
  2. 从索引 (city,name,age) 取下一个记录,同样取出这三个字段的值,作为结果集的一部分直接返回;
  3. 重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。

img

explain 结果如下:

img

Extra 字段里面多了“Using index”,表示的就是使用了覆盖索引,性能上会快很多。

上述查询语句中需要 city,name,age 三个字段,为了提升排序效率就创建联合索引,但维护索引也是需要代价的,所以需要慎重考虑。

七.扩展

order by limit使用何种排序?
  • 直接利用索引避免排序:用于有索引且回表效率高的情况下
  • 快速排序算法:如果没有索引大量排序的情况下
  • 堆排序算法:如果没有索引排序量不大的情况下

推荐阅读:MySQL:关于排序order by limit值不稳定的说明

根据时间排序是否会排序?

1、无条件查询如果只有 order by create_time,即便create_time上有索引,也不会使用到。因为优化器认为走二级索引再去回表成本比全表扫描排序更高。所以选择走全表扫描,然后根据具体情况选择全字段排序或是 rowid排序。

-- 没有索引的情况下
explain select * from t_user_recharge order by f_create_time;

img

alter table t_user_recharge add index idx_time(f_create_time);

增加索引后,查看执行计划,仍然走的是全表扫描。

img

2、无条件查询但是是 order by create_time limit m。如果m值较小,是可以走索引的。因为优化器认为根据索引有序性去回表查数据,然后得到m条数据,就可以终止循环,那么成本比全表扫描小,则选择走二级索引。即便没有二级索引,mysql 针对 order by limit 也做了优化,采用堆排序。

explain select * from t_user_recharge order by f_create_time limit 10;

img

SET optimizer_trace='enabled=on';

select * from t_user_recharge order by f_create_time limit 100;
SELECT * FROM information_schema.OPTIMIZER_TRACE;

查看 OPTIMIZER_TRACE 部分结果如下图所示:

img

字段加上 group by,如何走排序?

在MySQL中,如果在GROUP BY语句后没有指定排序方式,那么MySQL将按照分组的字段进行升序排序(ASC)。如果需要使用不同的排序方式对结果进行排序,则可以使用ORDER BY子句来指定。

还是以上文的表t作为测试表,来验证下述情况。

1、如果是 group by a,a上不能使用索引的情况,是走 rowid 排序。

SET optimizer_trace='enabled=on';

select name,count(name) from t group by name;
SELECT * FROM information_schema.OPTIMIZER_TRACE\G

查看 OPTIMIZER_TRACE 部分结果如下图所示:

img

  1. 2、如果是 group by a limit,a不能使用索引的情况,是走堆排序。
SET optimizer_trace='enabled=on';

select name,count(name) from t group by name limit 100;
SELECT * FROM information_schema.OPTIMIZER_TRACE\G

img

select * from t where city in (“杭州”," 苏州 ") order by name limit 100; 这个 SQL 语句是否需要排序?有什么方案可以避免排序?

虽然有 (city,name) 联合索引,对于单个 city 内部,name 是递增的。但是由于这条 SQL 语句不是要单独地查一个 city 的值,而是同时查了"杭州"和" 苏州 "两个城市,因此所有满足条件的 name 就不是递增的了。也就是说,这条 SQL 语句需要排序。

这里,我们要用到 (city,name) 联合索引的特性,把这一条语句拆成两条语句,执行流程如下:

  1. 执行 select * from t where city=“杭州” order by name limit 100; 这个语句是不需要排序的,客户端用一个长度为 100 的内存数组 A 保存结果。
  2. 执行 select * from t where city=“苏州” order by name limit 100; 用相同的方法,假设结果被存进了内存数组 B。
  3. 现在 A 和 B 是两个有序数组,然后你可以用归并排序的思想,得到 name 最小的前 100 值,就是我们需要的结果了。 ( 不能拘泥于MySQL本身,或者SQL语句本身,完全可以分开获取数据,在应用程序内存里面进行处理 )

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1291501.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

了解linux网络时间服务器

本章主要介绍网络时间服务器。 使用chrony配置时间服务器 配置chrony客户端向服务器同步时间 20.1 时间同步的必要性 些服务对时间要求非常严格&#xff0c;例如&#xff0c;图20-1所示的由三台服务器搭建的ceph集群。 这三台服务器的时间必须保持一致&#xff0c;如果不一致…

10 单词接龙

题目描述 单词接龙的规则是: 可用于接龙的单词首字母必须要前一个单词的尾字母相同; 当存在多个首字母相同的单词时&#xff0c;取长度最长的单词&#xff0c;如果长度也相等&#xff0c;则取字典序最小的单词;已经参与接龙的单词不能重复使用。 现给定一组全部由小写字母组成…

【开发技能】-解决visio交叉线(跨线)交叉点弯曲问题

问题 平时工作中使用visio作图时&#xff0c;经常会遇到交叉线在相交时会形成一个弯曲弓形&#xff0c;这十分影响视图效果。可以采用下面的方法消除弓形。 方法 第一步&#xff1a;菜单栏--设计---连接线 第二步&#xff1a;选中这条交叉线---点击显示跨线 最终问题得到解决…

C#图像处理OpenCV开发指南(CVStar,09)——边缘识别之Scharr算法的实例代码

1 边缘识别之Scharr算法 算法文章很多&#xff0c;不再论述。 1.1 函数原型 void Cv2.Scharr(src,dst,ddepth,dx,dy,scale,delta,borderType&#xff09; 1.2 参数说明 src 代表原始图像。dst 代表目标图像。ddepth 代表输出图像的深度。CV_16Sdx 代表x方向上的求导阶数…

23款奔驰E350eL升级小柏林音响 13个扬声器 590w

小柏林之声音响是13个喇叭1个功放&#xff0c;功率是590W&#xff0c;对应普通音响来说&#xff0c;已经是上等了。像著名的哈曼卡顿音响&#xff0c;还是丹拿音响&#xff0c;或者是BOSE音响&#xff0c;论地位&#xff0c;论音质柏林之声也是名列前茅。 升级小柏林音响&#…

java多人聊天

服务端 package 多人聊天;import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList;…

qt 5.15.2 主窗体菜单工具栏树控件功能

qt 5.15.2 主窗体菜单工具栏树控件功能 显示主窗体效果&#xff1a; mainwindow.h文件内容&#xff1a; #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QFileDialog> #include <QString> #include <QMessageBox>#inc…

深入理解Sentinel系列-2.Sentinel原理及核心源码分析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

CAP理论详解

引言 随着分布式系统在现代应用中的广泛应用&#xff0c;工程师们不得不面对诸如数据一致性、可用性和分区容错性等问题。CAP定理作为分布式系统设计的基石之一&#xff0c;为我们提供了在这些问题之间做出权衡的理论依据。本文将深入探讨CAP定理的技术细节、先进性&#xff0…

深度学习记录--广播(Broadcasting)

什么是广播&#xff1f; 广播(Broadcasting)&#xff0c;在python中是一种矩阵初等运算的手段&#xff0c;用于将一个常数扩展成一个矩阵&#xff0c;使得运算可行 广播的作用 比如&#xff1a; 一个1*n的矩阵要和常数b相加&#xff0c;广播使得常数b扩展成一个1*n的矩阵 …

mysql的几种索引

mysql索引的介绍可以mysql官网的词汇表中搜索&#xff1a; https://dev.mysql.com/doc/refman/8.0/en/glossary.html mysql可以在表的一列、或者多列上创建索引&#xff0c;索引的类型可以选择&#xff0c;如下&#xff1a; 普通索引&#xff08;KEY&#xff09; 普通索引可…

SQLserver截取字符串

当我们存的数据是json的时候可以全部取出在模糊查询但是有多个重复数据的时候就没办法准确的模糊出来这个时候我们就需要用的字符串截取 --创建函数create FUNCTION [dbo].[Fmax] (str varchar(50),start VARCHAR(50),length VARCHAR(50)) RETURNS varchar(max) AS BEGINDEC…

McBSP接口概念和使用

copy McBSP包括一个数据通道和一个控制通道&#xff0c;通过7个引脚与外部设备连接。数据发送引脚DX负责数据的发送&#xff0c;数据接收引脚DR负责数据的接收&#xff0c;发送时钟引脚CLKX&#xff0c;接收时钟引脚CLKR&#xff0c;发送帧同步引脚FSX和接收帧同步引脚FSR提供…

Python语言基础知识(一)

文章目录 1、Python内置对象介绍2、标识符与变量3、数据类型—数字4、数据类型—字符串与字节串5、数据类型—列表、元组、字典、集合6、运算符和表达式7、运算符和表达式—算术运算符8、运算符和表达式—关系运算符9.1、运算符和表达式— 成员测试运算符in9.2、运算符和表达式…

外贸平台自建站的教程?做海洋建站的好处?

外贸平台自建站怎么做&#xff1f;搭建网站的具体流程有哪些&#xff1f; 作为外贸从业者&#xff0c;借助互联网平台自建站点已经成为推广业务、拓展市场的一种重要手段。海洋建站将为您提供一份详尽的外贸平台自建站的教程&#xff0c;助您在网络空间中展现您的企业魅力。 …

RocketMQ-RocketMQ⾼性能核⼼原理分析

一、源码环境搭建 1、主要功能模块 ​ RocketMQ的官方Git仓库地址&#xff1a;https://github.com/apache/rocketmq 可以用git把项目clone下来或者直接下载代码包。 ​ 也可以到RocketMQ的官方网站上下载指定版本的源码&#xff1a; http://rocketmq.apache.org/dowloading/…

新能源车交直流充电解释

交流充电&#xff1a; 国家电网输出的电都是交流电&#xff0c;如下图所示&#xff0c;具有正弦切换规律的 而电动车的电池只能接受直流电&#xff0c;因此需要首先把交流电转换成直流电才能充进汽车电池&#xff0c;这就需要到了转换器OBC&#xff08;on-board Charger&#…

PPOCRv3检测模型和识别模型的训练和推理

PPOCRv3检测模型和识别模型的训练和推理 文章目录 PPOCRv3检测模型和识别模型的训练和推理前言一、环境安装1&#xff0c;官方推荐环境&#xff1a;2&#xff0c;本机GPU环境 二、Conda虚拟环境1.Win10安装Anaconda32.使用conda创建虚拟环境 三、安装PPOCR环境1&#xff0c;安装…

基于ssm人事管理信息系统论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本人事管理信息系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息…

跨境电商危机公关:应对负面舆情的策略优化

随着跨境电商的快速发展&#xff0c;企业在全球市场中面临的竞争与挑战也日益复杂。在这个数字时代&#xff0c;负面舆情一旦爆发&#xff0c;可能对企业形象和经营造成深远影响。 因此&#xff0c;跨境电商企业需要建立有效的危机公关策略&#xff0c;以迅速、果断、有效地应…