1.存储引擎基于表级别设计,每个表可以设置不同的存储引擎
存储引擎的功能
数据读写
数据安全和一致性
提高性能
热备份
自动故障恢复
高可用支持
2.查看当前数据库支持的存储引擎
show engines
FEDERATED
MRG_MYISAM
MyISAM
BLACKHOLE
CSV
MEMORY
ARCHIVE
InnoDB
PERFORMANCE_SCHEMA
3.查看默认存储引擎
select @@default_storage_engine;
4.你了解的存储引擎 MyISAM InnoDB MEMORY CSV
到8.0以后 只有innodb
第三方的存储引擎(下一代数据库的雏形)
RocksDB Myrocks TokuDB
它们的功能特点是 压缩比高,数据的插入性能高 其他和innodb没有区别
还有向 PerconaDB 的默认引擎是 XtraDB, MariaDB 的默认引擎是innodb
5.InnoDB 存储引擎核心特性说明(和 Myisam的区别)
支持事务
支持行级锁定(数据行级别)
支持MVCC(多版本并发控制)
支持外键
支持热备份和恢复
支持自动故障恢复(ACSR)
支持复制(多线程,GTID,MTS)
6.建表时指定存储引擎
create table c1(id int) engine=myisam ;
也可以在my.cnf中设置默认的存储引擎
default_storage_engine=innodb;
7.查看表的存储引擎参数,3种方法
show create table 表名
SHOW TABLE STATUS LIKE '表名'\G;
select table_schema,table_name ,engine from information_schema.tables where table_schema not in ('sys','mysql','information_schema','performance_schema');
8.存储引擎的修改
alter table 表名 engine=innodb;
9.整理碎片
碎片的产生一般是 删除数据的操作造成的,因为删除操作是逐行进行的,当数据行被删除时,空间不会释放,所以会产生碎片,影响性能
前提存储引擎是innodb,跟上面的一样
alter table 表名 engine=innodb;
在业务不繁忙的时候做,会出现短暂的锁表
还有一种有风险而且效率低的操作就是把数据导出来,truncate掉表,然后重新导入
将zabbix库中的所有表,innodb替换为tokudb
select concat("alter table zabbix.",table_name," engine tokudb;") from
information_schema.tables where table_schema='zabbix' into outfile '/tmp/tokudb.sql';
10.innodb存储引擎物理存储结构
a.myisam一共包含3个文件
*.frm,存储表结构;
*.MYD,存储数据行;
*.MYI,存储索引。
b.innodb的文件的组成
ibdata1:系统数据字典信息(统计信息),UNDO表空间等数据
ib_logfile0 ~ ib_logfile1: REDO日志文件,事务日志文件。
ibtmp1: 临时表空间磁盘位置,存储临时表(一般多表查询会使用到临时表)
frm:存储表的列信息
ibd:表的数据行和索引
11.表空间(Table space)
表空间的概念弄5.5版本开始,从oracle借鉴过来的,为了解决数据跨盘使用的问题
ibdata1 整个库的统计信息 + undo
ibd 数据行和索引
a.共享表空间(ibdata1-N)
5.5版本的默认模式,5.6中变成独立表空间
需要将所有数据存储到同一个表空间中 ,管理比较混乱
5.5版本出现的管理模式,也是默认的管理模式。
5.6版本以,共享表空间保留,只用来存储:数据字典信息,undo,临时表。
5.7 版本,临时表被独立出来了
8.0版本,undo也被独立出去了
具体变化参考官方文档:
https://dev.mysql.com/doc/refman/5.6/en/innodb-architecture.html
共享表空间设置(在搭建MySQL时,初始化数据之前设置到参数文件中)
[(none)]>select @@innodb_data_file_path;
[(none)]>show variables like '%extend%';
innodb_data_file_path=ibdata1:512M:ibdata2:512M:autoextend
innodb_autoextend_increment=64
上面的参数的使用需在 数据库刚刚初始化的时候使用,例如:
mysqld --initialize-insecure --user=mysql --basedir=xxx ......
innodb_data_file_path=ibdata1:512M:ibdata2:512M:autoextend
b.独立表空间
从5.6,默认表空间不再使用共享表空间,替换为独立表空间。
主要存储的是用户数据
存储特点为:一个表一个ibd文件,存储数据行和索引信息
对于 innodb的一张表由下面价格文件组成
frm 列信息(表结构)
idb 数据行
ibdata1 统计信息
MySQL的存储引擎日志:
Redo Log: ib_logfile0 ib_logfile1,重做日志
Undo Log: ibdata1 ibdata2(存储在共享表空间中),回滚日志
临时表:ibtmp1,在做join union操作产生临时数据,用完就自动清理
独立表空间设置问题
select @@innodb_file_per_table;
+-------------------------+
| @@innodb_file_per_table |
+-------------------------+
| 1 |
+-------------------------+
每张表对应一个文件,如果改成0就成了共享表空间了
如果要修改则使用
set global innodb_file_per_table=0 #这辈子也不会修改
12.独立表空间迁移
我们知道一个innodb的表包含3个文件
frm 列信息(表结构)
idb 数据行
ibdata1 统计信息
如果我们把 这3个文件复制到另外一台主机上,是否这个表在这个新主机上就能打开呢?
答案是否定的,因为还有dbdata1这里面有统计信息,如果又偏要使用这个复制的方式呢,此时就用到独立表空间迁移了
使用一下步骤
a.拿到原建表语句
show create table t100w;
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| t100w | CREATE TABLE `t100w` (
`id` int(11) DEFAULT NULL,
`num` int(11) DEFAULT NULL,
`k1` char(2) COLLATE utf8mb4_bin DEFAULT NULL,
`k2` char(4) COLLATE utf8mb4_bin DEFAULT NULL,
`dt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY `idx_k2` (`k2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
b.到目标机器上,新建一个库,然后使用上面的建表语句建立一个空表
c.将原主机上的 frm 还有 ibd 文件复制到新主机上,替换ibd同名的文件(因为刚刚使用建表命令,所以在新主机上会存在这个ibd文件)
注意:此时不能和直接删除原来的ibd文件,要使用下面的命令
alter table t100w dicard tablespace;
使用过这条命令以后 原来的idb就会被删除,此时将原主机的idb复制过来,注意文件的权限
再使用
alter table t100w import tablespace;
后即完成了操作
13.事务可以理解为 对于交易类有关的业务,保持其操作的完整性和安全性的
14.事务的ACID特性
Atomic(原子性)
所有语句作为一个单元全部成功执行或全部取消。不能出现中间状态。
Consistent(一致性)
如果数据库在事务开始时处于一致状态,则在执行该事务期间将保留一致状态。
Isolated(隔离性)
事务之间不相互影响。
Durable(持久性)
事务成功完成后,所做的所有更改都会准确地记录在数据库中。所做的更改不会丢失。
15.事务的生命周期(事务控制语句)
事务的开始
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
事务的结束
commit:提交事务
完成一个事务,一旦事务提交成功 ,就说明具备ACID特性了。
rollback :回滚事务
将内存中,已执行过的操作,回滚回去
16.标准的事务语句
DML insert update delete
下面看一个例子
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from City where id <5;
+----+----------------+-------------+----------+------------+
| ID | Name | CountryCode | District | Population |
+----+----------------+-------------+----------+------------+
| 1 | Kabul | AFG | Kabol | 1780000 |
| 2 | Qandahar | AFG | Qandahar | 237500 |
| 3 | Herat | AFG | Herat | 186800 |
| 4 | Mazar-e-Sharif | AFG | Balkh | 127800 |
+----+----------------+-------------+----------+------------+
4 rows in set (0.00 sec)
mysql> update City set CountryCode ='CCC' where id <3;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0
mysql> select * from City where id <5;
+----+----------------+-------------+----------+------------+
| ID | Name | CountryCode | District | Population |
+----+----------------+-------------+----------+------------+
| 1 | Kabul | CCC | Kabol | 1780000 |
| 2 | Qandahar | CCC | Qandahar | 237500 |
| 3 | Herat | AFG | Herat | 186800 |
| 4 | Mazar-e-Sharif | AFG | Balkh | 127800 |
+----+----------------+-------------+----------+------------+
4 rows in set (0.01 sec)
现在回滚刚才的update操作
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from City where id <5;
+----+----------------+-------------+----------+------------+
| ID | Name | CountryCode | District | Population |
+----+----------------+-------------+----------+------------+
| 1 | Kabul | AFG | Kabol | 1780000 |
| 2 | Qandahar | AFG | Qandahar | 237500 |
| 3 | Herat | AFG | Herat | 186800 |
| 4 | Mazar-e-Sharif | AFG | Balkh | 127800 |
+----+----------------+-------------+----------+------------+
4 rows in set (0.01 sec)
再次 update
mysql> update City set CountryCode ='CCC' where id <3;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0
mysql> select * from City where id <5;
+----+----------------+-------------+----------+------------+
| ID | Name | CountryCode | District | Population |
+----+----------------+-------------+----------+------------+
| 1 | Kabul | CCC | Kabol | 1780000 |
| 2 | Qandahar | CCC | Qandahar | 237500 |
| 3 | Herat | AFG | Herat | 186800 |
| 4 | Mazar-e-Sharif | AFG | Balkh | 127800 |
+----+----------------+-------------+----------+------------+
4 rows in set (0.00 sec)
这次 commit;
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from City where id <5;
+----+----------------+-------------+----------+------------+
| ID | Name | CountryCode | District | Population |
+----+----------------+-------------+----------+------------+
| 1 | Kabul | CCC | Kabol | 1780000 |
| 2 | Qandahar | CCC | Qandahar | 237500 |
| 3 | Herat | AFG | Herat | 186800 |
| 4 | Mazar-e-Sharif | AFG | Balkh | 127800 |
+----+----------------+-------------+----------+------------+
4 rows in set (0.00 sec)
17.自动提交机制
select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
1 row in set (0.00 sec)
在5.5 以上的版本,不需要手工begin,只要你执行的是一个DML,会自动在前面加一个begin命令,紧接着会在这条DML语句后自动加上commit,所以这样就没法回滚了
关闭自动提交有2种方法,一种是会话级别的,另一种是全局级别的
set autocommit=0; 当前会话
set global autocommit=0; 全局(需所有会话重连后生效)
永久生效修改my.cnf
vim /etc/my.cnf
autocommit=0
注:
自动提交是否打开,一般在有事务需求的MySQL中,将其关闭
18.隐式提交语句
用于隐式提交的 SQL 语句:
begin
a
b
begin 此时相当于在这个begin前自动加一个commit
非事务语句:
DDL语句: (ALTER、CREATE 和 DROP)
DCL语句: (GRANT、REVOKE 和 SET PASSWORD)
锁定语句:(LOCK TABLES 和 UNLOCK TABLES)
导致隐式提交的语句
示例:
TRUNCATE TABLE
LOAD DATA INFILE
SELECT FOR UPDATE
所以在使用事务时,尽量使用DML,夹带其他的语句会导致隐式提交
19.事务的acid 如何保证?
一些概念名词
redo可以理解为mysql在处理数据时,会先在内存中处理,然后写到硬盘上,但因为mysql在处理一行数据时,并不是只处理这一行,而是把这一行所在数据的数据页(16k)提取出来处理,redo就是记录在内存中处理的过程(redo log相对于16k的数据页体积很小),redo log会先于数据写入硬盘(以提高commit的效率)。如果有一天服务器断电了,在内存中的数据会因为断电而消失,但是可以靠redo log恢复到断电前的状态,具体过程为 innodb会比较idb中的LSN和redolog中的LSN,如果二者不一致会触发CSR机制,从而恢复数据到断电前的状态,完成了CSR的前滚操作,随后会触发CKPT进而将内存中数据写入硬盘
上面的话用专业的说法就是
MySQL : 在启动时,必须保证redo日志文件和数据文件LSN必须一致, 如果不一致就会触发CSR,最终保证一致
情况一:
我们做了一个事务,begin;update;commit.
1.在begin ,会立即分配一个TXID=tx_01.
2.update时,会将需要修改的数据页(dp_01,LSN=101),加载到data buffer中
3.DBWR线程,会进行dp_01数据页修改更新,并更新LSN=102
4.LOGBWR日志写线程,会将dp_01数据页的变化+LSN+TXID存储到redobuffer
5. 执行commit时,LGWR日志写线程会将redobuffer信息写入redolog日志文件中,基于WAL原则,
在日志完全写入磁盘后,commit命令才执行成功,(会将此日志打上commit标记)
6.假如此时宕机,内存脏页没有来得及写入磁盘,内存数据全部丢失
7.MySQL再次重启时,必须要redolog和磁盘数据页的LSN是一致的.但是,此时dp_01,TXID=tx_01磁盘是LSN=101,dp_01,TXID=tx_01,redolog中LSN=102
MySQL此时无法正常启动,MySQL触发CSR.在内存追平LSN号,触发ckpt,将内存数据页更新到磁盘,从而保证磁盘数据页和redolog LSN一值.这时MySQL正长启动
以上的工作过程,我们把它称之为基于REDO的"前滚操作"
20.redo的3个功能
记录内存页的变化
提供快速的持久化的功能(WAL)
CSR的过程中实现redo(前滚)的操作,保证磁盘数据页和redo日志LSN的一致
redo log ---> 重做日志 相关文件为ib_logfile0~1 默认50M , 轮询使用
redo log buffer ---> redo内存区域 存储着数据页的变化信息+数据页当时的LSN号
ibd ----> 存储 数据行和索引
data buffer pool --->缓冲区池,数据和索引的缓冲
LSN : 日志序列号
ibd,data buffer pool,redolog,redo buffer 这4类文件都有自己的LSN记录
MySQL 每次数据库启动,都会比较ibd和redolog的LSN,必须要求两者LSN一致数据库才能正常启动,如果不一致就会触发CSR,最终保证一致
WAL : write ahead log 日志优先写的方式实现持久化(日志优先于数据写入磁盘的一种机制)
脏页: 内存脏页,内存中发生了修改,没写入到磁盘之前,我们把内存页称之为脏页.
CKPT:Checkpoint,检查点,就是将脏页刷写到磁盘的动作
TXID: 事务号,InnoDB会为每一个事务生成一个事务号,伴随着整个事务的生命周期.
事务日志 redo 重做日志
主要功能是保证 D, AC也有一定作用
它记录了内存数据页的变化
redo log的刷写策略
当触发commit时会,刷新当前事务的redo buffer到磁盘还会顺便将一部分redo buffer中没有提交的事务日志也刷新到磁盘
刚才描述的是当commit成功了的状态,mysql如何保证事务的acid的,那如果是没有commit就断电了呢?
21.undo是什么?
回滚日志
在事务ACID过程中,实现的是“A” 原子性的作用
另外CI也依赖于Undo
记录了数据修改之前的状态
在rollback时,将数据恢复到修改之前的状态
在CSR实现的是,将redo当中记录的未提交的时候进行回滚.
undo提供快照技术,保存事务修改之前的数据状态.保证了MVCC,隔离性,mysqldump的热备
22.锁
“锁”顾名思义就是锁定的意思。
“锁”的作用是什么?
在事务ACID过程中,“锁”和“隔离级别”一起来实现“I”隔离性和"C" 一致性 (redo也有参与).
悲观锁:行级锁定(行锁)
谁先操作某个数据行,就会持有<这行>的(X)锁.
乐观锁: 没有锁
23.锁定实验,
在终端A和终端B上线确定 事务的自动提交已关闭
set global autocommit=0;
然后在A终端使用city表进行update语句操作,如:
update City set countrycode='CHN' where id =100
此时在B终端也执行此语句发现会卡住
当A终端 commit后,B终端的操作得以继续。
此时A终端继续update id=101 ,然后B终端 update id=102,那么此时B终端的update是否能成功呢?
答案是否定的。因为虽然innodb是行级锁,但是实际会锁定一个区域。如果update id = 99呢,结果是可以update的,
所以由此可见这个锁是锁id = 101以后的
24.隔离级别
查看当前模式
select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
永久修改编辑My.cnf
在服务端配置中加入 transaction_isolation=read-uncommitted
transaction_isolation=read-uncommitted (RU)
此种模式下,假如有2终端A和B
A终端开启事务后update 后,在没有commit的情况下,B终端可以读到未commit的数据
transaction_isolation=read-committed (RC)
此种模式下,假如有2终端A和B
A终端开启事务后update 后,在没有commit的情况下,B终端不可以读到未commit的数据
但是此种模式下会有一个问题就是如果A频繁的update commit 会导致B每次查询的结果都不一样,这个称为不可重复读
transaction_isolation=repeatable-read (RR)
此种模式下,假如有2终端A和B
A终端开启事务后update 后,无论是否commit的情况下,B终端读到的数据始终是B终端的session开始的那一刻的数据。
无论A终端怎么样update 是否commit,都只是B终端session开始的那一刻的数据。
RU : 读未提交,可脏读。不能在业务中出现。
RC : 读已提交,可能出现幻读,不可重复读(不能在金融业务中出现,大部门互联网企业中可以接受,因为没有那2把锁,会提高高并发性能),可以防止脏读.
RR : 可重复读,通过MVCC 快照技术解决了不可重复读,可能会出现"幻读"现象 ,可以利用 GAP(间隙锁)+NextLock(下键锁)进行避免,前提是有索引
补充: 在RC级别下,可以减轻GAP+NextLock锁的问题,但是会出现幻读现象,一般在为了读一致性会在正常select后添加for update语句.但是,请记住执行完一定要commit 否则容易出现所等待比较严重
25.什么是幻读呢
下面来看个例子
首先以RC模式进行
先建一张表
create table test2 (id int,name varchar(10));
插入一些数据
insert into test2 values (1,'a'),(2,'b'),(3,'b'),(4,'d');
现在这张表里有以下这些数据
select * from test2;
+------+------+
| id | name |
+------+------+
| 1 | a |
| 2 | b |
| 3 | b |
| 4 | d |
+------+------+
现在另起一个终端B,之前用的叫终端A
在终端B中执行update test set name='x' where id > 2;
再切回A终端 输入
insert into test2 values (5,'e');
commit;
此时再切回终端B
commit;
问题来了,现在在终端B select * from test2 会出现怎样的结果
select * from test2;
+------+------+
| id | name |
+------+------+
| 1 | a |
| 2 | b |
| 3 | x |
| 4 | x |
| 5 | e |
+------+------+
这里出现的 5 e 就是幻读
再看另外一种幻读
此时在终端A继续插入
insert into test2 values(8,'x'),(9,'y');
commit;
select * from test2;
+------+------+
| id | name |
+------+------+
| 1 | a |
| 2 | b |
| 3 | x |
| 4 | x |
| 5 | e |
| 8 | x |
| 9 | y |
+------+------+
此时切换到B终端
update test2 set name='zs' where id>2;
切换回终端A 插入2条数据
insert into test2 values(6,'x'),(7,'y');
commit;
此时切换到B终端
commit;
select * from test2;
+------+------+
| id | name |
+------+------+
| 1 | a |
| 2 | b |
| 3 | zs |
| 4 | zs |
| 5 | zs |
| 8 | zs |
| 9 | zs |
| 6 | x |
| 7 | y |
+------+------+
此时 6 和 7 没有被更新,所以这也是一种幻读
就是明明已经update id>2的内容了,但实际的结果却是update的结果与期望不符
所以为了避免幻读,就要使用 GAP(间隙锁)+NextLock(下键锁) 这2把锁来避免幻读(前提是RR模式)
1
2
3
4
5
8
9
6
7
在这些id中会锁定 >9 的叫做NextLock
锁定5-8的叫做gap
而这2个锁实际上不锁定数据行,锁定的是索引,所以前提还要有索引
总结一下就是RR模式 有索引 配合2把锁 gap nextlock 可以避免幻读
下面通过实例验证下
建表 插数据 加索引
use world;
create table gap(id int, name varchar(10));
insert into gap values (1,'a'),(2,'b'),(3,'c'),(7,'x'),(8,'y');
select * from gap;
+------+------+
| id | name |
+------+------+
| 1 | a |
| 2 | b |
| 3 | c |
| 7 | x |
| 8 | y |
+------+------+
alter table gap add index id(id);
开启另外一个终端成为终端B ,现在的是终端A
终端B做update操作
update gap set name='aaa' where id >2
切回终端A
insert into gap values (4,'abc');
此时就会卡住,因为2把锁在起作用
此时我们知道,如果手动向A终端插入一行数据,即使commit了,B终端也看不到,那么只有B终端断开重连,或者手动commit一下就可以看到A终端的插入的数据了。update也有同样的效果
26.innodb的核心参数
默认引擎为innodb
default_storage_engine=innodb
表空间模式为独立表空间
innodb_file_per_table=1
下面的2个参数要在mysql初始化前设置
---------------------------------
共享表空间文件个数和大小
innodb_data_file_path=ibdata1:512M:ibdata2:512M:autoextend #一共使用2个文件 ibdata1 还有 ibdata2 ,用满后自动扩展
共享表空间自动扩展的大小(MB)
innodb_autoextend_increment=64
---------------------------------
"双一"模式
select @@innodb_flush_log_at_trx_commit;
+----------------------------------+
| @@innodb_flush_log_at_trx_commit |
+----------------------------------+
| 1 |
+----------------------------------+
innodb_flush_log_at_trx_commit=1
此项控制redo往磁盘刷写的策略
redolog刷入磁盘需要经历2步,首先从mysql的redo buffer中刷入OS的buffer,然后在刷入磁盘中
当设置为1时,每当事务提交时就会实时的往磁盘里面刷,就算宕机也不会导致数据丢失
当设置为0时,无论事务是否提交,都会每隔1秒写入OS的buffer,每隔1秒将OS的buffer的数据同步到磁盘(fsync)。所以宕机会丢失数据
当设置为2时,事务提交后会直接写入OS的buffer,每隔1秒将OS的buffer的数据同步到磁盘(fsync)。所以宕机会丢失数据
如果考虑数据安全则设置为1,追求性能不考虑数据完整性可使用0
缓冲区池设置
select @@innodb_buffer_pool_size;
show engine innodb status\G
innodb_buffer_pool_size
一般建议最多是物理内存的 75-80%
数据刷入方式
Innodb_flush_method=(O_DIRECT, fsync)
默认值是 fsync
这个参数控制的是 buffer pool 还有redo buffer 是否经过OS buffer 刷入磁盘
fsync 是buffer pool 还有redo buffer 都经过 OS buffer 刷入 Disk
而 O_DIRECT 是 buffer poll 跳过OS buffer 直接刷入磁盘 redo buffer 经过 OS buffer 刷入 Disk
而 O_DSYNC 是 redo buffer 跳过OS buffer 直接刷入磁盘 buffer poll 经过 OS buffer 刷入 Disk
考虑到buffer poll不经过OS buffer 会提高安全性,减轻内存压力
最高安全模式
innodb_flush_log_at_trx_commit=1
Innodb_flush_method=O_DIRECT
最高性能:
innodb_flush_log_at_trx_commit=0
Innodb_flush_method=fsync
redo日志有关的参数
innodb_log_buffer_size=16777216
innodb_log_file_size=50331648
innodb_log_files_in_group = 3
脏页刷写策略
innodb_max_dirty_pages_pct=75
脏页最大的内存占用比例,在buffer poll中,脏页占用内存75%以后会触发写入硬盘
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)