2023年Web服务器基准测试:NodeJS vs Java vs Rust vs Go

news2024/11/23 18:33:37

现在是2023年,是时候进行一次新的Web服务器基准测试了!

结果对我来说有些出乎意料!

一个Web服务器必须能够处理大量请求,尽管瓶颈在于IO。这次我决定比较最流行的、速度极快的现代框架的性能。

以下是有关实现细节的许多详细信息。如果您只想了解结果,请直接前往文章底部以节省时间。如果您对测试的执行方式感兴趣,请继续阅读 😃

我们的瓶颈将是一个带有一些数据的Postgres数据库。因此,我们的Web服务器必须能够在不阻塞的情况下尽可能多地处理每秒请求数。在接收到数据后,它应该将答案序列化为JSON并返回有效的HTTP响应。

将测试哪些技术

  • Spring WebFlux + Kotlin
    • 传统的JVM
    • GraalVM原生映像
  • NodeJS + Express
  • Rust
    • Rocket
    • Actix Web

我的配置

CPU:Intel Core i7–9700K 3.60 GHz(8个核心,无超线程)

RAM:32 GB

操作系统:Windows 11(版本22h2)

Docker:Docker for Desktop(Windows版)版本4.16.3,启用了WSL2支持-由Microsoft提供的默认资源配置

Postgres:使用以下Docker命令启动

docker run -d --name my-postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_DB=goods -p 5432:5432 postgres:15.2

数据库连接池大小:最多50个连接。每个Web服务器都将使用此数量以保持相同的条件。

数据库初始化:

CREATE TABLE goods(
    id BIGSERIAL NOT NULL PRIMARY KEY ,
    name VARCHAR(255) NOT NULL,
    description TEXT NULL,
    price INT NOT NULL
);

INSERT INTO goods (name, description, price)
VALUES ('Apple', 'Red fruit', 100),
       ('Orange', 'Orange fruit', 150),
       ('Banana', 'Yellow fruit', 200),
       ('Pineapple', 'Yellow fruit', 250),
       ('Melon', 'Green fruit', 300);

我决定不在数据库中存储太多的数据,以避免对数据库性能产生影响。我假设Postgres能够缓存所有的数据,并且大部分时间都将用于网络IO。

基准测试工具集

工具:k6(v0.42.0)

脚本:

import http from 'k6/http';

export default function () {
    http.get('http://localhost:8080/goods');
}

每次运行测试的命令都是相同的:

k6 run --vus 1000 --duration 30s .\load_testing.js

由于我们将有一个简单的端点,它将以 JSON 格式从 DB 返回数据列表,因此我刚刚添加了一个获取测试。 每个框架的所有测试都使用相同的脚本和命令运行。

NodeJS + Express Web 服务器实现

NodeJS version:

node --version
v18.14.0

package.json:

{
  "name": "node-api-postgres",
  "version": "1.0.0",
  "description": "RESTful API with Node.js, Express, and PostgreSQL",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.2",
    "pg": "^8.9.0"
  }
}

index.js:

const express = require('express')
const app = express()
const port = 8080

const { Pool } = require('pg')
const pool = new Pool({
    host: 'localhost',
    port: 5432,
    user: 'postgres',
    password: 'postgres',
    database: 'goods',
    max: 50,
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 2000,
})

const getGoods = (request, response) => {
    pool.query('SELECT * FROM goods', (error, results) => {
        if (error) {
            throw error
        }
        response.status(200).json(results.rows)
    })
}

app.get('/goods', getGoods)

pool.connect((err, client, done) => {
    console.log(err)

    app.listen(port, () => {
        console.log(`App running on port ${port}.`)
    })
})

Spring WebFlux + R2DBC + Kotlin 实现

Java version:

java --version
openjdk 17.0.5 2022-10-18
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)

gradle file:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
 id("org.springframework.boot") version "3.0.2"
 id("io.spring.dependency-management") version "1.1.0"
 id("org.graalvm.buildtools.native") version "0.9.18"
 kotlin("jvm") version "1.7.22"
 kotlin("plugin.spring") version "1.7.22"
}

group = "me.alekseinovikov.goods"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
 mavenCentral()
}

dependencies {
 implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
 implementation("org.springframework.boot:spring-boot-starter-webflux")
 implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
 implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
 implementation("org.jetbrains.kotlin:kotlin-reflect")
 implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
 runtimeOnly("org.postgresql:postgresql")
 runtimeOnly("org.postgresql:r2dbc-postgresql")
 testImplementation("org.springframework.boot:spring-boot-starter-test")
 testImplementation("io.projectreactor:reactor-test")
}

tasks.withType<KotlinCompile> {
 kotlinOptions {
  freeCompilerArgs = listOf("-Xjsr305=strict")
  jvmTarget = "17"
 }
}

tasks.withType<Test> {
 useJUnitPlatform()
}

application.properties:

spring.r2dbc.url=r2dbc:postgresql://postgres:postgres@localhost:5432/goods
spring.r2dbc.pool.enabled=true
spring.r2dbc.pool.max-size=50
spring.r2dbc.pool.max-idle-time=30s
spring.r2dbc.pool.max-create-connection-time=30s

Application code:

@SpringBootApplication
class GoodsApplication

fun main(args: Array<String>) {
 runApplication<GoodsApplication>(*args)
}

@Table("goods")
class Good(
    @field:Id
    val id: Int,

    @field:Column("name")
    val name: String,

    @field:Column("description")
    val description: String,

    @field:Column("price")
    val price: Int
) {
}

interface GoodsRepository: R2dbcRepository<Good, Int> {
}

@RestController
class GoodsController(private val goodsRepository: GoodsRepository) {

    @GetMapping("/goods")
    suspend fun getGoods(): Flow<Good> = goodsRepository.findAll().asFlow()

}

为 fat jar 构建:

gradlew clean build

为 GraalVM 本机映像构建:

gradlew clean nativeCompile

Rust + Rocket 实现

cargo.toml:

[package]
name = "rust-goods"
version = "0.1.0"
edition = "2021"

[dependencies]
rocket = { version = "0.5.0-rc.2", features = ["secrets", "tls", "json"] }
serde_json = "1.0"
refinery = { version = "0.8", features = ["tokio-postgres"]}

[dependencies.rocket_db_pools]
version = "0.1.0-rc.2"
features = ["sqlx_postgres"]

Rocket.toml:

[default]
secret_key = "6XrKhVEP3gFMqmfhUzDdSYDthOLU442TjSCnz7sPEYE="
port = 8080

[default.databases.goods]
url = "postgres://postgres:postgres@localhost/goods"
max_connections = 50

main.rs:

#[macro_use]
extern crate rocket;

use rocket::serde::Serialize;
use rocket::serde::json::Json;
use rocket::State;
use rocket_db_pools::{Connection, Database};
use rocket_db_pools::sqlx::{self};
use rocket_db_pools::sqlx::{Error, Postgres, Row};
use rocket_db_pools::sqlx::postgres::PgRow;
use sqlx::FromRow;

#[derive(Serialize, Debug, PartialOrd, PartialEq, Clone)]
#[serde(crate = "rocket::serde")]
pub struct Good {
    pub id: usize,
    pub name: String,
    pub description: String,
    pub price: usize,
}

struct Repository;

impl Repository {
    pub(crate) fn new() -> Repository {
        Repository
    }

    pub(crate) async fn list(&self, mut db: Connection<Goods>) -> Vec<Good> {
        sqlx::query_as::<Postgres, Good>("SELECT id, name, description, price FROM goods")
            .fetch_all(&mut *db)
            .await
            .unwrap()
    }
}

impl<'r> FromRow<'r, PgRow> for Good {
    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
        let id: i64 = row.try_get("id")?;
        let name = row.try_get("name")?;
        let description = row.try_get("description")?;
        let price: i32 = row.try_get("price")?;

        Ok(Good { id: id as usize, name, description, price: price as usize })
    }
}

#[get("/goods")]
async fn list(repository: &State<Repository>,
              db: Connection<Goods>) -> Json<Vec<Good>> {
    Json(repository
        .list(db)
        .await)
}

#[derive(Database)]
#[database("goods")]
struct Goods(sqlx::PgPool);

#[launch]
async fn rocket() -> _ {
    let rocket = rocket::build();

    rocket.attach(Goods::init())
        .manage(Repository::new())
        .mount("/", routes![
            list,
        ])
}

编译:

cargo build --release

Rust + Actix Web 实现

Cargo.toml:

[package]
name = "rust-actix-goods"
version = "0.1.0"
edition = "2021"

[dependencies]
actix-web = "4"
derive_more = "0.99.17"
config = "0.13.3"
log = "0.4"
env_logger = "0.10.0"
deadpool-postgres = { version = "0.10.5", features = ["serde"] }
dotenv = "0.15.0"
serde = { version = "1.0.152", features = ["derive"] }
tokio-pg-mapper = "0.2.0"
tokio-pg-mapper-derive = "0.2.0"
tokio-postgres = "0.7.7"

.env:

RUST_LOG=error
SERVER_ADDR=0.0.0.0:8080
PG.USER=postgres
PG.PASSWORD=postgres
PG.HOST=localhost
PG.PORT=5432
PG.DBNAME=goods
PG.POOL.MAX_SIZE=50
PG.SSL_MODE=Disable

main.rs:

mod config {
    use serde::Deserialize;
    #[derive(Debug, Default, Deserialize)]
    pub struct ExampleConfig {
        pub server_addr: String,
        pub pg: deadpool_postgres::Config,
    }
}

mod models {
    use serde::{Deserialize, Serialize};
    use tokio_pg_mapper_derive::PostgresMapper;

    #[derive(Deserialize, PostgresMapper, Serialize)]
    #[pg_mapper(table = "goods")]
    pub struct Good {
        pub id: i64,
        pub name: String,
        pub description: String,
        pub price: i32,
    }
}

mod db {
    use deadpool_postgres::Client;
    use tokio_pg_mapper::FromTokioPostgresRow;

    use crate::models::Good;

    pub async fn select_goods(client: &Client) -> Vec<Good> {
        let _stmt = "SELECT id, name, description, price FROM goods";
        let stmt = client.prepare(&_stmt).await.unwrap();

        client
            .query(
                &stmt,
                &[],
            )
            .await
            .unwrap()
            .iter()
            .map(|row| Good::from_row_ref(row).unwrap())
            .collect::<Vec<Good>>()
    }
}

mod handlers {
    use actix_web::{web, Error, HttpResponse};
    use deadpool_postgres::{Client, Pool};

    use crate::db;

    pub async fn get_goods(
        db_pool: web::Data<Pool>,
    ) -> Result<HttpResponse, Error> {
        let client: Client = db_pool.get().await.unwrap();
        let goods = db::select_goods(&client).await;
        Ok(HttpResponse::Ok().json(goods))
    }
}

use ::config::Config;
use actix_web::{web, App, HttpServer, middleware::Logger};
use dotenv::dotenv;
use handlers::get_goods;
use tokio_postgres::NoTls;

use crate::config::ExampleConfig;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenv().ok();
    env_logger::init();

    let config_ = Config::builder()
        .add_source(::config::Environment::default())
        .build()
        .unwrap();

    let config: ExampleConfig = config_.try_deserialize().unwrap();

    let pool = config.pg.create_pool(None, NoTls).unwrap();

    let server = HttpServer::new(move || {
        App::new()
            .wrap(Logger::default())
            .app_data(web::Data::new(pool.clone()))
            .service(web::resource("/goods").route(web::get().to(get_goods)))
    })
        .bind(config.server_addr.clone())?
        .run();
    println!("Server running at http://{}/", config.server_addr);

    server.await
}

编译:

cargo build --release

Go + Echo 实现

go.mod:

module goods-go

go 1.20

require (
 github.com/labstack/echo/v4 v4.10.0
 github.com/lib/pq v1.10.7
)

require (
 github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
 github.com/labstack/gommon v0.4.0 // indirect
 github.com/mattn/go-colorable v0.1.13 // indirect
 github.com/mattn/go-isatty v0.0.16 // indirect
 github.com/valyala/bytebufferpool v1.0.0 // indirect
 github.com/valyala/fasttemplate v1.2.2 // indirect
 golang.org/x/crypto v0.2.0 // indirect
 golang.org/x/net v0.4.0 // indirect
 golang.org/x/sys v0.3.0 // indirect
 golang.org/x/text v0.5.0 // indirect
 golang.org/x/time v0.2.0 // indirect
)

main.go:

package main

import (
 "database/sql"
 "fmt"
 "github.com/labstack/echo/v4"
 _ "github.com/lib/pq"
 "log"
 "net/http"
)

const (
 host     = "localhost"
 port     = 5432
 user     = "postgres"
 password = "postgres"
 dbname   = "goods"
)

var db *sql.DB

type Good struct {
 ID          int    `json:"id"`
 Name        string `json:"name"`
 Description string `json:"description"`
 Price       int    `json:"price"`
}

func getAllGoods(c echo.Context) error {
 rows, err := db.Query("SELECT id, name, description, price FROM goods")
 if err != nil {
  return c.JSON(http.StatusInternalServerError, err)
 }
 defer rows.Close()

 goods := make([]Good, 0)
 for rows.Next() {
  var good Good
  if err := rows.Scan(&good.ID, &good.Name, &good.Description, &good.Price); err != nil {
   log.Fatal(err)
  }

  goods = append(goods, good)
 }

 return c.JSON(http.StatusOK, goods)
}

func main() {
 psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
  "password=%s dbname=%s sslmode=disable",
  host, port, user, password, dbname)
 var err error
 db, err = sql.Open("postgres", psqlInfo)
 if err != nil {
  log.Fatal(err)
 }
 db.SetMaxOpenConns(50)

 e := echo.New()
 // Routes
 e.GET("/goods", getAllGoods)

 // Start server
 e.Logger.Fatal(e.Start(":8080"))
}

编译:

go build -ldflags "-s -w"

基准测试

最后,在我们对环境和实现有了一定了解后,我们准备开始进行基准测试。

结果比较:

NameRequests Per SecondRequests TotalMemory Usage
Node Js3233.37773997772105MB
Spring JVM4457.39441134162675MB
Spring Native Image3854.41882116267211MB
Rust Rocket5592.4429516857348MB
Rust Actix5312.35606516031033.5MB
Go Echo13545.85960240725472.1MB

哎呀!当我想到这个基准测试的想法时,我认为Rust会是胜利者。第二名将由JVM和Go获得。但事实的发展有点出乎意料。

如果我在代码实现上犯了任何错误,请写下评论告诉我。我尽力遵循官方文档中的示例。从我的角度来看,我的所有代码都是异步和非阻塞的。我检查了几次。但我是人,如果有更好的方法可以提高特定技术的性能,请告诉我。

Go是最快的。似乎Echo库是其中一个原因。

Rust的速度可疑地慢。我尝试了几次,检查了2个框架,但未能使其更快。

传统JVM相当快(至少比NodeJS快),但仍然消耗大量内存。

GraalVM Native Image在减少内存消耗但保留了JVM的成熟工具集方面很有价值。

NodeJS是最慢的,也许是因为它的单线程事件循环。这里没有什么新鲜的。

结论

我不是说这个特定的用例展示了技术或工具的整体性能。我知道不同的工具有不同的用途。但是,所有这些语言和运行时都用于Web服务器开发,并在云服务器中运行。因此,我决定进行这个基准测试,以了解使用不同技术堆栈开发简单微服务时的速度和资源容忍程度。

对我来说,结果有些令人震惊,因为我预计Rust会获胜。但Go向我展示了这门语言和Echo框架在编写具有大量IO的简单微服务方面非常出色。

遗憾的是,JVM似乎无法达到相同的性能/资源消耗,从而在开发云Web服务方面变得不那么吸引人。但GraalVM Native Image给了它第二次机会。它的速度不及Go或Rust,但减少了对内存的需求。

因此,如果你能雇佣很多Gopher来参与你的下一个项目,你可能能在基础设施上节省一些钱。

如果你喜欢我的文章,点赞,关注,转发!

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

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

相关文章

安装 vue-element-admin,安装报错解决

安装 vue-element-admin 克隆项目 git clone https://github.com/PanJiaChen/vue-element-admin.git //(英文的)git clone -b i18n https://github.com/PanJiaChen/vue-element-admin.git // 这个克隆出来是有中英文切换的进入项目目录 cd vue-element-admin建议不要用 cnpm…

chatgpt赋能python:Python交互编程入门指南

Python交互编程入门指南 Python是一种高级编程语言&#xff0c;适合初学者和专业人士使用。Python的互动式编程方式为开发人员提供了快速反馈的环境&#xff0c;从而实现更便捷和高效的开发过程。在本文中&#xff0c;我们将介绍Python的交互编程&#xff0c;为您提供Python编…

达梦数据库运维常用归档、sql日志、dexp与dimp操作

目录 一、归档文件配置... 3 二、sql⽇志的开启和关闭以及基本的操作... 3 三、执行计划... 5 四、工具和命令行数据库物理、逻辑备份&#xff0c;还原... 6 1、工具物理备份... 6 2、命令行联机备份... 8 3、命令行脱机备份... 8 4、工具物理还原... 8 5、命令行DMRM…

C++之动态分配new 删除delete 初始化memset

文章目录 1.动态分配 new1.引言2.new的实现 2.删除 delete3.初始化 memset 1.动态分配 new 1.引言 用new创建数组的优势&#xff1a;由于new创建的对象是在运行时确立的&#xff0c;所以有着具体情况具体分析的优点&#xff0c;那么什么叫做具体情况具体分析呢&#xff1f; 举…

linux led 驱动

前言 今天是儿童节&#xff0c;挣个奖牌给小孩玩玩。 在 linux 驱动大家庭中&#xff0c;LED 驱动算是个儿童&#xff0c;今天就写写他吧。正好之前写过他的婴儿时期《i.MX6ULL 裸机点亮 LED》&#xff0c;记得那时候他还穿着开裆裤呢&#xff0c;裸鸡嘛。 ioremap() 裸机程…

某点资讯Signature纯算逆向

本篇主要是介绍一些工作的运用熟练性&#xff0c;以及跟踪堆栈去看是否做一些其他操作等&#xff1a; 抓包: signature 为加密值&#xff1b; 先上trace下堆栈及加密 我们把结果base64下&#xff0c;看结果是否一致&#xff0c;来判断base64是否魔改 验证base64为标准&…

新规之下产业园区如何合理收费水电费用

一、政策背景 2018年3月30日&#xff0c;国家发改委发布《国家发展改革委关于降低一般工商业电价有关事项的通知》。明确提出进一步规范和降低电网环节收费&#xff0c;一是提高两部制电价的灵活性&#xff1b;二是全面清理规范电网企业在输配电价之外的收费项目&#xff0c;重…

三极管 场效应管

NPN 高电平导通 PNP 低电平导通 N-MOS 高电平导通 P-MOS 低电平导通 1. NPN 三极管&#xff0c;对于软件工程师来说&#xff0c;只需要关注数字电路&#xff0c;即: 导通还是截止&#xff0c;高电平还是低电平。至于三级管内部如何构成的&#xff0c;以及串了多少个电阻&am…

智能安全配电装置在老旧建筑防火中的应用

【摘要】现代社会的发展离不开电能&#xff0c;随着电能应用的广泛性&#xff0c;对用电安全有了更高的要求。近些年来&#xff0c;用电安全形式严峻&#xff0c;尤其是一些老旧建筑中因用电而引起的火灾事故频发&#xff0c;造成一系列严重的损失&#xff0c;严重影响着民众的…

PCout(n) -- STM32F103RCT6 位带操作

1. 使用位带操作控制GPIO口的输入、输出模式&#xff0c;以及输出的电平高、低 注&#xff1a;位带操作一般是操作单独的一个bit 位&#xff0c;而&&#xff0c;| 则可操作多个bit位&#xff0c;看自己的需求吧。&#xff08;不懂&&#xff0c;| 是什么意思的自行问度…

MySQL-6-多表操作

一、复制表 格式 create table 表名 select查询语句注意&#xff1a;复制成新表时&#xff0c;键值&#xff08;pri,index等等&#xff09;索引不会同步复制案例 mysql> create table t2 select name,sex,age from user;二、多表查询 2.1、 多表查询–>连接查询 将2个…

Vue.js 比较重要知识点总结一

概述 谈一谈你对 Vue.js 的响应式数据的理解Vue3 出现解决了什么问题&#xff1f;它有哪些优势&#xff1f;Vue3 新特性有哪些vue2 和 vue3 的响应式有什么区别&#xff1f; 谈一谈你对 Vue.js 的响应式数据的理解 Vue 2.x 对象类型&#xff1a;通过 object.defineProperty(…

MySQL——初窥门径

前言 六一&#xff1f;作为一个大小孩当然是快快乐乐搞技术啦~在这篇文章中&#xff0c;荔枝会梳理SQL语句的基本语法以及MySQL中的函数、约束。多表关系以及查询、事务和事务隔离级别等内容&#xff0c;大致内容归属于MySQL基础知识&#xff0c;荔枝又弄了一篇万字长文哈哈哈哈…

R:GAM非线性回归曲线拟合与散点密度图绘制

作者:CSDN @ _养乐多_ 本文将介绍使用R语言以及GAM模型,绘制回归曲线和散点密度图。 文章目录 一、R语言脚本二、色带一、R语言脚本 install.packages("ggpointdensity") install.packages("ggplot2") insta

IPD发展史

随着IPD&#xff08;集成产品开发&#xff09;在IBM、华为等企业取得了巨大的成功&#xff0c;其他行业也开始在相关新产品研发中初步引入IPD的研发管理理念及模式&#xff0c;对IPD在行业的应用进行初步的探索和研究。 为了更好地应用IPD &#xff0c;不仅要对它的理念和思想理…

浅谈高等学校能源监控管理体系建设

摘要&#xff1a;现代高校担当着人才培养&#xff0c;社会服务和文化传承与创新的光荣使命。高校低碳节能工作是加快建设“和谐社会”、“绿色校园”的重要举措 。当前高校以“数字化能源监测平台”为重心 &#xff0c;积极推动能源管理的转型 。该文总结高校能源监管平台建设的…

达梦数据库作业调度及警报配置

目录 作业... 4 创建代理环境... 4 1、命令行创建及删除... 4 2、客户端创建及删除... 4 操作员... 5 1、命令行创建及删除... 5 2、客户端创建及删除... 5 作业... 6 一、命令行... 6 1、命令行创建作业... 6 2、命令行修改作业... 7 3、启动或暂停作业... 7 4、…

MATLAB与深度学习:Neural Network Toolbox和Deep Learning Toolbox的使用和模型设计

章节一&#xff1a;引言 在当今人工智能和深度学习的时代&#xff0c;MATLAB作为一种功能强大的科学计算和数据分析工具&#xff0c;在深度学习领域也发挥着重要作用。本文将重点介绍MATLAB中的两个关键工具&#xff1a;Neural Network Toolbox和Deep Learning Toolbox的使用和…

chatgpt赋能python:Python主页的SEO优化

Python主页的SEO优化 Python是一种简单易学、高效灵活的编程语言。其主页Python.org是全球最受欢迎的编程语言之一的官方网站。但是&#xff0c;即使是最著名的网站也需要进行优化&#xff0c;以便在搜索引擎中排名更高。在本文中&#xff0c;我们将探讨如何通过SEO来改进Pyth…

关系型数据库一些概念性的知识点总结

在当今数据驱动的世界中&#xff0c;信息为王。从客户资料到金融交易&#xff0c;每个组织都依赖数据来做出明智的决策并在竞争中保持领先地位。但随着数据量以前所未有的速度增长&#xff0c;管理和分析所有这些信息很快就会变得不堪重负。这就是关系数据库的用武之地。 关系数…