2024年11月

MySQL--DAY04

索引

定义

索引是在数据库
表的字段
上添加的,是为了
提高查询效率
存在的一种机制。

一张表的一个字段可以添加一个索引,当然,多个字段联合起来也可以添加索引。

索引相当于一本书的目录,是为了
缩小扫描范围
而存在的一种机制。

对于一本字典来说,查找某个汉字有两种方式:

  • 第一种方式:一页一页挨着找,直到找到为止,这种查找方式属于全字典扫描。效率比较低。
  • 第二种方式:先通过目录(索引)去定位一个大概的位置,然后直接定位到这个位置,做局域性扫描,缩小扫描的范围,快速的查找。这种查找方式属于通过索引检索,效率较高。
	t_user
	id(idIndex)	name(nameIndex)	email(emailIndex)		address  (emailAddressIndex)
	----------------------------------------------------------------------------------
	1				zhangsan...
	2				lisi
	3				wangwu
	4				zhaoliu
	5				hanmeimei
	6				jack

	select * from t_user where name = 'jack';

以上的这条SQL语句会去name字段上扫描,为什么?

因为查询条件是:name='jack'

如果name字段上没有添加索引(目录),或者说没有给name字段创建索引,

MySQL会进行全扫描,会将name字段上的每一个值都比对一遍。效率比较低。

MySQL在查询方面主要就是两种方式:

第一种方式:全表扫描

第二种方式:根据索引检索。

注意:

在实际中,汉语字典前面的目录是
排序
的,按照a b c d e f....排序,

为什么排序呢?因为只有排序了才会有区间查找这一说!(缩小扫描范围

其实就是扫描某个区间罢了!)

在mysql数据库当中
索引也是需要排序
的,并且这个索引的排序和TreeSet

数据结构相同。TreeSet(TreeMap)底层是一个自平衡的二叉树!在mysql

当中索引是一个
B-Tree
数据结构。

遵循左小右大原则存放。采用中序遍历方式遍历取数据。

原理

假设有一张用户表:t_user

	id(PK)					name						每一行记录在硬盘上都有物理存储编号
	----------------------------------------------------------------------------------
	100						zhangsan					0x1111
	120						lisi						0x2222
	99						wangwu						0x8888
	88						zhaoliu						0x9999
	101						jack						0x6666
	55						lucy						0x5555
	130						tom							0x7777

提醒1:在任何数据库当中
主键
上都会
自动添加索引对象
,id字段上自动有索引,因为id是PK。另外在mysql当中,一个字段上如果有
unique约束
的话,也会
自动创建索引对象

提醒2:在任何数据库当中,任何一张表的
任何一条记录
在硬盘存储上都有一个硬盘的
物理存储编号

提醒3:在mysql当中,索引是一个
单独的对象
,不同的存储引擎以不同的形式存在,在MyISAM存储引擎中,索引存储在一个.MYI文件中。在InnoDB存储引擎中索引存储在一个逻辑名称叫做tablespace的当中。在MEMORY存储引擎当中索引被存储在内存当中。不管索引存储在哪里,索引在mysql当中都是一个

的形式存在。(自平衡二叉树:
B-Tree

在mysql当中,主键上,以及unique字段上都会自动添加索引的!!!!

什么条件下,我们会考虑给字段添加索引呢?

  • 条件1:数据量庞大(到底有多么庞大算庞大,这个需要测试,因为每一个硬件环境不同)
  • 条件2:该字段经常出现在where的后面,以条件的形式存在,也就是说这个字段总是被扫描。
  • 条件3:该字段很少的DML(insert delete update)操作。(因为DML之后,索引需要重新排序。)

建议不要随意添加索引,因为索引也是需要维护的,太多的话反而会降低系统的性能。

建议通过主键查询,建议通过unique约束的字段进行查询,效率是比较高的。

创建和删除索引

创建索引:

mysql> create index emp_ename_index on emp(ename);

给emp表的ename字段添加索引,起名:emp_ename_index

删除索引:

mysql> drop index emp_ename_index on emp;

将emp表上的emp_ename_index索引对象删除。

查看SQL语句是否使用索引

	mysql> explain select * from emp where ename = 'KING';
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
	| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
	|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
	+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

扫描14条记录:说明没有使用索引。type=ALL

	mysql> create index emp_ename_index on emp(ename);

	mysql> explain select * from emp where ename = 'KING';
	+----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-------------+
	| id | select_type | table | type | possible_keys   | key             | key_len | ref   | rows | Extra       |
	+----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-------------+
	|  1 | SIMPLE      | emp   | ref  | emp_ename_index | emp_ename_index | 33      | const |    1 | Using where |
	+----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-------------+

索引失效

索引什么时候失效?

失效的第1种情况

select * from emp where ename like '%T';

ename上即使添加了索引,也不会走索引,为什么?

原因是因为模糊匹配当中以“%”开头了!

尽量避免模糊查询的时候以“%”开始。

这是一种优化的手段/策略。

		mysql> explain select * from emp where ename like '%T';
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
		| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
		|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

失效的第2种情况

使用or的时候会失效,如果使用or那么要求
or两边的条件字段都要有索引
,才会走索引,如果其中一边有一个字段没有索引,那么另一个字段上的索引也会实现。所以这就是为什么不建议使用or的原因。

mysql> explain select * from emp where ename = 'KING' or job = 'MANAGER';
	+----+-------------+-------+------+-----------------+------+---------+------+------+-------------+
	| id | select_type | table | type | possible_keys   | key  | key_len | ref  | rows | Extra       |
	+----+-------------+-------+------+-----------------+------+---------+------+------+-------------+
	|  1 | SIMPLE      | emp   | ALL  | emp_ename_index | NULL | NULL    | NULL |   14 | Using where |
	+----+-------------+-------+------+-----------------+------+---------+------+------+-------------+

失效的第3种情况

使用复合索引的时候,没有使用左侧的列查找,索引失效

什么是复合索引?

两个字段,或者更多的字段联合起来添加一个索引,叫做复合索引。

		create index emp_job_sal_index on emp(job,sal);
		
		mysql> explain select * from emp where job = 'MANAGER';
		+----+-------------+-------+------+-------------------+-------------------+---------+-------+------+-------------+
		| id | select_type | table | type | possible_keys     | key               | key_len | ref   | rows | Extra       |
		+----+-------------+-------+------+-------------------+-------------------+---------+-------+------+-------------+
		|  1 | SIMPLE      | emp   | ref  | emp_job_sal_index | emp_job_sal_index | 30      | const |    3 | Using where |
		+----+-------------+-------+------+-------------------+-------------------+---------+-------+------+-------------+
		
		mysql> explain select * from emp where sal = 800;
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
		| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
		|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

失效的第4种情况

在where当中索引列参加了运算,索引失效。

		mysql> create index emp_sal_index on emp(sal);

		explain select * from emp where sal = 800;
		+----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+
		| id | select_type | table | type | possible_keys | key           | key_len | ref   | rows | Extra       |
		+----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+
		|  1 | SIMPLE      | emp   | ref  | emp_sal_index | emp_sal_index | 9       | const |    1 | Using where |
		+----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+

		mysql> explain select * from emp where sal+1 = 800;
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
		| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
		|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

失效的第5种情况

在where当中索引列使用了函数

		explain select * from emp where lower(ename) = 'smith';
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
		| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
		|  1 | SIMPLE      | emp   | ALL  | NULL          | NULL | NULL    | NULL |   14 | Using where |
		+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

失效的第...种情况

索引分类

  1. 单一索引:一个字段上添加索引。
  2. 复合索引:两个字段或者更多的字段上添加索引。
  3. 主键索引:主键上添加索引。
  4. 唯一性索引:具有unique约束的字段上添加索引。
    .....

注意:唯一性比较弱的字段上添加索引用处不大。

视图

什么是视图?

view:站在不同的角度去看待同一份数据。

创建和删除视图对象

	表复制:
	mysql> create table dept2 as select * from dept;

	dept2表中的数据:
	mysql> select * from dept2;
	+--------+------------+----------+
	| DEPTNO | DNAME      | LOC      |
	+--------+------------+----------+
	|     10 | ACCOUNTING | NEW YORK |
	|     20 | RESEARCH   | DALLAS   |
	|     30 | SALES      | CHICAGO  |
	|     40 | OPERATIONS | BOSTON   |
	+--------+------------+----------+

创建视图对象:

	create view dept2_view as select * from dept2;

删除视图对象:

	drop view dept2_view;	

注意:只有DQL语句才能以view的形式创建。

create view view_name as // 这里的语句必须是DQL语句;

视图的作用

我们可以面向视图对象进行增删改查,对视图对象的增删改查,会导致
原表被操作

(视图的特点:通过对视图的操作,会影响到原表数据。)

	//面向视图查询
	select * from dept2_view; 

	// 面向视图插入
	insert into dept2_view(deptno,dname,loc) values(60,'SALES', 'BEIJING');

	// 查询原表数据
	mysql> select * from dept2;
	+--------+------------+----------+
	| DEPTNO | DNAME      | LOC      |
	+--------+------------+----------+
	|     10 | ACCOUNTING | NEW YORK |
	|     20 | RESEARCH   | DALLAS   |
	|     30 | SALES      | CHICAGO  |
	|     40 | OPERATIONS | BOSTON   |
	|     60 | SALES      | BEIJING  |
	+--------+------------+----------+

	// 面向视图删除
	mysql> delete from dept2_view;

	// 查询原表数据
	mysql> select * from dept2;
	Empty set (0.00 sec)
	

	// 创建视图对象
	create view 
		emp_dept_view
	as
		select 
			e.ename,e.sal,d.dname
		from
			emp e
		join
			dept d
		on
			e.deptno = d.deptno;

	// 查询视图对象
	mysql> select * from emp_dept_view;
	+--------+---------+------------+
	| ename  | sal     | dname      |
	+--------+---------+------------+
	| CLARK  | 2450.00 | ACCOUNTING |
	| KING   | 5000.00 | ACCOUNTING |
	| MILLER | 1300.00 | ACCOUNTING |
	| SMITH  |  800.00 | RESEARCH   |
	| JONES  | 2975.00 | RESEARCH   |
	| SCOTT  | 3000.00 | RESEARCH   |
	| ADAMS  | 1100.00 | RESEARCH   |
	| FORD   | 3000.00 | RESEARCH   |
	| ALLEN  | 1600.00 | SALES      |
	| WARD   | 1250.00 | SALES      |
	| MARTIN | 1250.00 | SALES      |
	| BLAKE  | 2850.00 | SALES      |
	| TURNER | 1500.00 | SALES      |
	| JAMES  |  950.00 | SALES      |
	+--------+---------+------------+

	// 面向视图更新
	update emp_dept_view set sal = 1000 where dname = 'ACCOUNTING';

	// 原表数据被更新
	mysql> select * from emp;
	+-------+--------+-----------+------+------------+---------+---------+--------+
	| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
	+-------+--------+-----------+------+------------+---------+---------+--------+
	|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |    NULL |     20 |
	|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 |
	|  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30 |
	|  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |    NULL |     20 |
	|  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30 |
	|  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |    NULL |     30 |
	|  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 1000.00 |    NULL |     10 |
	|  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000.00 |    NULL |     20 |
	|  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 1000.00 |    NULL |     10 |
	|  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30 |
	|  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100.00 |    NULL |     20 |
	|  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |    NULL |     30 |
	|  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |    NULL |     20 |
	|  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1000.00 |    NULL |     10 |
	+-------+--------+-----------+------+------------+---------+---------+--------+

实际开发中的作用?

《方便,简化开发,利于维护》

		create view 
			emp_dept_view
		as
			select 
				e.ename,e.sal,d.dname
			from
				emp e
			join
				dept d
			on
				e.deptno = d.deptno;

假设有一条非常复杂的SQL语句,而这条SQL语句需要在不同的位置上反复使用。

每一次使用这个sql语句的时候都需要重新编写,很长,很麻烦,怎么办

可以把这条复杂的SQL语句以视图对象的形式新建。

在需要编写这条SQL语句的位置直接使用视图对象,可以大大简化开发。

并且
利于后期的维护
,因为修改的时候也只需要修改一个位置就行,只需要修改视图对象所映射的SQL语句。

我们以后面向视图开发的时候,使用视图的时候可以像使用table一样。

可以对视图进行增删改查等操作。视图不是在内存当中,视图对象也是存储在
硬盘
上的,不会消失。

再提醒一下

视图对应的语句
只能是DQL语句
。但是视图对象创建完成之后,可以对视图进行增删改查等操作。

小插曲:

增删改查,又叫做:CRUD。

CRUD是在公司中程序员之间沟通的术语。一般我们很少说增删改查。

一般都说CRUD。

C:Create(增)
R:Retrive(查:检索)
U:Update(改)
D:Delete(删)

DBA常用命令?

重点掌握:
数据的导入和导出

数据的备份

其它命令了解一下即可。

数据导出

注意:在windows的dos命令窗口中:

mysqldump study>D:\study.sql -uroot -p123456

可以导出指定的表吗?

mysqldump study emp>D:\study.sql -uroot -p123456

数据导入

注意:需要先登录到mysql数据库服务器上。

然后创建数据库:

create database study;

使用数据库:

use study

然后初始化数据库:source D:\study.sql

数据库设计三范式

什么是数据库设计三范式?

数据库表的
设计依据
。教你怎么进行数据库表的设计。

数据库设计三范式有几类?

3个。

  1. 第一范式:要求任何一张表必须有主键,每一个字段原子性不可再分。
  2. 第二范式:建立在第一范式的基础之上,要求所有非主键字段完全依赖主键,不要产生部分依赖。
  3. 第三范式:建立在第二范式的基础之上,要求所有非主键字段直接依赖主键,不要产生传递依赖。

声明:三范式是面试官经常问的,所以一定要熟记在心!

设计数据库表的时候,按照以上的范式进行,可以
避免表中数据的冗余,空间的浪费

第一范式

最核心,最重要的范式,所有表的设计都需要满足。

必须有主键
,并且每一个字段都是
原子性
不可再分。

	学生编号 	学生姓名 			联系方式
	---------------------------------------------------
	1001		张三			zs@gmail.com,1359999999
	1002		李四			ls@gmail.com,13699999999
	1001		王五			ww@163.net,13488888888

以上是学生表,满足第一范式吗?

不满足,第一:没有主键。第二:联系方式可以分为邮箱地址和电话

	学生编号(pk) 		学生姓名		邮箱地址				联系电话
	--------------------------------------------------------------------
	1001				张三			zs@gmail.com		1359999999
	1002				李四			ls@gmail.com		13699999999
	1003				王五			ww@163.net			13488888888

第二范式

建立在第一范式的基础之上,要求
所有非主键字段必须完全依赖主键

不要产生部分依赖

学生编号 		学生姓名 	教师编号 	教师姓名
----------------------------------------------------
1001			张三		001			王老师
1002			李四		002			赵老师
1003			王五		001			王老师
1001			张三		002			赵老师

这张表描述了学生和老师的关系:(1个学生可能有多个老师,1个老师有多个学生)

这是非常典型的:
多对多
关系!

分析以上的表是否满足第一范式?

不满足第一范式。

怎么满足第一范式呢?修改

	学生编号+教师编号(pk)			学生姓名  		教师姓名
	----------------------------------------------------
	1001			001				张三			王老师
	1002			002				李四			赵老师
	1003			001				王五			王老师
	1001			002				张三			赵老师

学生编号 教师编号,两个字段联合做主键,
复合主键
(PK: 学生编号+教师编号)

经过修改之后,以上的表满足了第一范式。但是满足第二范式吗?

不满足,“张三”依赖1001,“王老师”依赖001,显然产生了
部分依赖

产生部分依赖有什么缺点?

数据冗余
了。
空间浪费
了。“张三”重复了,“王老师”重复了。

为了让以上的表满足第二范式,你需要这样设计:

使用三张表来表示多对多的关系!!!!

		学生表
		学生编号(pk)			学生名字
		------------------------------------
		1001					张三
		1002					李四
		1003					王五
		
		教师表
		教师编号(pk)		教师姓名
		--------------------------------------
		001					王老师
		002					赵老师

		学生教师关系表
		id(pk)					学生编号(fk)			教师编号(fk)
		-----------------------------------------------------------
		1						1001						001
		2						1002						002
		3						1003						001
		4						1001						002

背口诀:

多对多怎么设计?

多对多,三张表,关系表两个外键!!!!!!!!!!!!!!!

第三范式

第三范式建立在第二范式的基础之上

要求所有非主键字典必须直接依赖主键,不要产生
传递依赖

		学生编号(PK) 		学生姓名 班级编号  		班级名称
	---------------------------------------------------------
		1001				张三		01			一年一班
		1002				李四		02			一年二班
		1003				王五		03			一年三班
		1004				赵六		03			一年三班

以上表的设计是描述:班级和学生的关系。很显然是1对多关系!

一个教室中有多个学生。

分析以上表是否满足第一范式?

满足第一范式,有主键。

分析以上表是否满足第二范式?

满足第二范式,因为主键不是复合主键,没有产生部分依赖。主键是
单一主键

分析以上表是否满足第三范式?

第三范式要求:不要产生传递依赖!

一年一班依赖01,01依赖1001,产生了传递依赖。

不符合第三范式的要求。产生了数据的冗余。

那么应该怎么设计一对多呢?

		班级表:一
		班级编号(pk)					班级名称
		----------------------------------------
		01								一年一班
		02								一年二班
		03								一年三班

		学生表:多

		学生编号(PK) 		学生姓名 	班级编号(fk)
		-------------------------------------------
		1001				张三			01			
		1002				李四			02			
		1003				王五			03			
		1004				赵六			03		

背口诀:

一对多,两张表,多的表加外键!!!!!!!!!!!!

总结

一对多:一对多,两张表,多的表加外键!!!!!!!!!!!!

多对多:多对多,三张表,关系表两个外键!!!!!!!!!!!!!!!

一对一:一对一放到一张表中不就行了吗?为啥还要拆分表?

在实际的开发中,可能存在一张表字段太多,太庞大。这个时候要拆分表。

一对一怎么设计?

			没有拆分表之前:一张表
				t_user
				id		login_name		login_pwd		real_name		email				address........
				---------------------------------------------------------------------------
				1			zhangsan		123				张三				zhangsan@xxx
				2			lisi			123				李四				lisi@xxx
				...
			
			这种庞大的表建议拆分为两张:
				t_login 登录信息表
				id(pk)		login_name		login_pwd	
				---------------------------------
				1				zhangsan		123			
				2				lisi			123			

				t_user 用户详细信息表
				id(pk)		real_name		email				address........	login_id(fk+unique)
				-----------------------------------------------------------------------------------------
				100			张三				zhangsan@xxx								1
				200			李四				lisi@xxx										2

口诀:一对一,外键唯一!!!!!!!!!!

叮嘱

数据库设计三范式是
理论
上的。

实践和理论有的时候有偏差。

最终的目的都是为了
满足客户的需求
,有的时候会拿冗余换执行速度。

因为在sql当中,表和表之间连接次数越多,效率越低。(笛卡尔积)

有的时候可能会存在冗余,但是为了
减少表的连接次数
,这样做也是合理的,并且对于开发人员来说,sql语句的编写难度也会降低。

面试的时候把这句话说上:他就不会认为你是初级程序员了!

在各平台应用开发过程中,随着业务的功能增加,不免会涉及到非公开的
API
依赖,针对某些应用或厂商系统的适配,每个版本都需要投入精力去排查,
CoCollider
可以让我们的适配效率从几个星期提升到几小时即可完成。

项目已开源:

☞ Github:https://www.github.com/iofomo/fireyer ☜

☞ Github:https://www.github.com/iofomo/wing ☜

如果您也喜欢,别忘了给我们点个星。

01. 写在前面


Android
平台(后续会支持
iOS
平台)日常开发过程中,不免会接触到一些
Hidden
(或私有函数和类)的类,
JVM
层通过反射,
Native
则通过查找函数
symbol
来完成。特别是涉及系统
Framework
层和底层库较多的实现(特别是安全产品和系统工具类应用),成为每次系统和厂商
ROM
更新适配的重灾区,严重影响适配效率。

以往,开发同学要么先一个个自行排查代码后再提交测试,这个过程重复,耗时且容易遗漏。要么先让测试同学先进行用例覆盖测试,然后开发同学通过
BUG
来进行分析,这会导致整个团队效率更加低下。

由于我们虚拟化产品的特性,工程中涉及系统和应用适配的接口众多,工作量很大,主要有两部分:

  1. 如何快速寻找到系统新增的模块,感兴趣的可以查看我们的另一个开源项目
    【ASeeker】
    https://www.github.com/iofomo/aseeker
  2. 如何快速定位现有使用的
    API
    是否已发生变化,这就是
    【CoCollider】
    要解决的问题。

CoCollider
已经在我们内部使用,极大提升我们系统适配效率,原本需要几个星期的工作量,现在只需几小时便可适配完成,原理简单,堪称适配效率神器。
Android 15
发布在即,现在分享给大家。

CoCollider
同样适用于其他平台(如:iOS)和某些深度定制的应用版本适配。

02. 我们需要

对于系统的
API
,我们的需求是:

  1. 类的属性是否有变化。
  2. 类的成员属性是否有变化。
  3. 类的方法属性是否有变化。
  4. Native
    库和函数是否存在。

对于开发工程师的工作量,首次需要在源码中添加注释标签,这个工作不可省略,却一劳永逸。后续需要在每次涉及适配的代码处加上
@CoCollider
标签就行。

Java代码标签格式:

格式:
// @CoCollider {class name},{-/+}{field name/method name},...

范例:
# 仅查看类是否变更
// @CoCollider android.utils.Abc
# 仅查看类所有成员是否变更
// @CoCollider android.utils.Abc,-*
# 查看类和某成员或方法是否变更(支持多个)
// @CoCollider android.utils.Abc,-mFile
// @CoCollider android.utils.Abc,-mFile,mName
// @CoCollider android/utils/Abc,-mFile
// @CoCollider android.utils.Abc,+getFile,-mName
// @CoCollider android/utils/Abc,+getFile,+getName,-mName

# 支持缺省自动填充:(按照顺序依次填充)
Class.forName("android.utils.Abc");// @CoCollider
# 等同于:// @CoCollider android.utils.Abc

ReflectUtils.getStaticField("android.utils.Abc", "mName");// @CoCollider ,-
# 等同于:// @CoCollider android.utils.Abc,-mName

Native 代码标签格式:

格式:
// @CoCollider ~{lib name},{-/+}{field name/method name},...
  
范例:
# 查看方法是否变更
// @CoCollider ~libc.so,+open
// @CoCollider ~/system/lib/libc.so,+open
// @CoCollider ~/system/lib/libc.so,+open,+close

# 支持缺省自动填充:(按照顺序依次填充)
utils_dlsym("libc.so", "open");// @CoCollider ~,+
# 等同于:// @CoCollider ~libc.so,+open

utils_dlsym("open");// @CoCollider ~libc.so,+
# 等同于:// @CoCollider ~libc.so,+open

03. 配置运行

  1. 电脑下载配置
    wing

  2. 手机安装
    Fireyer
    ,或集成
    CoCollider
    模块的应用,链接
    adb

  3. 扫描代码:

    # 命令格式:扫描当前目录下的代码(默认支持 h/c/cpp/java/kt/aidl,也可以追加文件类型)
    $ wing -cocollider scan
    $ wing -cocollider scan /home/space
    # 或
    $ python ./cocollider.py scan
    $ python ./cocollider.py scan /home/space
    # 在当前目录输出结果
    >>> cocollider-scan-20241023-112044.txt
    
  4. 解析运行:

    # 命令格式:调用 Fireyer 应用
    $ wing -cocollider run /home/cocollider-scan-20241023-112044.txt
    # 或
    $ python ./cocollider.py run /home/cocollider-scan-20241023-112044.txt
    
    # 在当前目录输出结果
    >>> cocollider-run-20241023-112044.txt
    
  5. 使用对比工具查看结果即可快速定位变更内容。

04. 扫描结果

cocollider-scan-20241023-112044.txt
中的文件内容为:

############################################################
= android.utils.Abc

- mFile
> application/fireyer/test/test1.java,14

- mName3
> application/fireyer/test/test1.java,14

- mPath
> application/fireyer/test/test1.java,14

############################################################
= android.utils.Abc1

- mFile
> application/fireyer/test/test1.java,5

...

05. 运行结果

cocollider-run-20241023-112044.txt
中的文件内容(已对
class

lib
库,
function

field

method
排序,便于使用对比工具对比)

############################################################
~ libwilhelm.so
[OK]

+ _ZN7androidxx6BufferE
[OK], public final class libcore.io.Linux
> cmpt/xxx/jni/xxx/jni/src/xxx.cpp,362

+ _ZN7android1xx6BufferE
[OK], public final class libcore.io.Linux
> cmpt/xxx/jni/xxx/jni/src/xxx.cpp,350


############################################################
= ohos.abilityshell.HarmonyApplication
[Fail]

- applicationHandler
[Fail]
> scene/xxx/xxx.java,36


############################################################
= ohos.system.Parameters
[Fail]

+ nativeGet
[Fail]
> scene/xxx.cpp,250

...

06. 对比结果

分别在不同版本系统运行
python cocollider.py run
之后,使用对比工具(如:
Beyond Compare
)对内容进行比较查看,可以快速找到新增,修改和删除项,从而可以快速进行排查和修复。

前言

对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了。

在某些业务场景下,如果一个请求中,需要同时写入多张表的数据。为了保证操作的原子性(要么同时成功,要么同时失败),避免数据不一致的情况,我们一般都会用到spring事务。

确实,spring事务用起来贼爽,就用一个简单的注解:
@Transactional
,就能轻松搞定事务。我猜大部分小伙伴也是这样用的,而且一直用一直爽。

但如果你使用不当,它也会坑你于无形。

今天我们就一起聊聊,事务失效的一些场景,说不定你已经中招了。不信,让我们一起看看。

一 事务不生效

1.访问权限问题

众所周知,java的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。

但如果我们在开发过程中,把有某些事务方法,定义了错误的访问权限,就会导致事务功能出问题,例如:

@Service
public class UserService {
    
    @Transactional
    private void add(UserModel userModel) {
         saveData(userModel);
         updateData(userModel);
    }
}

我们可以看到add方法的访问权限被定义成了
private
,这样会导致事务失效,spring要求被代理方法必须是
public
的。

说白了,在
AbstractFallbackTransactionAttributeSource
类的
computeTransactionAttribute
方法中有个判断,如果目标方法不是public,则
TransactionAttribute
返回null,即不支持事务。

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
    }

    // The method may be on an interface, but we need attributes from the target class.
    // If the target class is null, the method will be unchanged.
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

    // First try is the method in the target class.
    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    if (txAttr != null) {
      return txAttr;
    }

    // Second try is the transaction attribute on the target class.
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
      return txAttr;
    }

    if (specificMethod != method) {
      // Fallback is to look at the original method.
      txAttr = findTransactionAttribute(method);
      if (txAttr != null) {
        return txAttr;
      }
      // Last fallback is the class of the original method.
      txAttr = findTransactionAttribute(method.getDeclaringClass());
      if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
      }
    }
    return null;
  }

也就是说,如果我们自定义的事务方法(即目标方法),它的访问权限不是
public
,而是private、default或protected的话,spring则不会提供事务功能。

2. 方法用final修饰

有时候,某个方法不想被子类重新,这时可以将该方法定义成final的。普通方法这样定义是没问题的,但如果将事务方法定义成final,例如:

@Service
public class UserService {

    @Transactional
    public final void add(UserModel userModel){
        saveData(userModel);
        updateData(userModel);
    }
}

我们可以看到add方法被定义成了
final
的,这样会导致事务失效。

为什么?

如果你看过spring事务的源码,可能会知道spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。

但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。

注意:如果某个方法是static的,同样无法通过动态代理,变成事务方法。

3.方法内部调用

有时候我们需要在某个Service类的某个方法中,调用另外一个事务方法,比如:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    //@Transactional
    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
        updateStatus(userModel);
    }

    @Transactional
    public void updateStatus(UserModel userModel) {
        doSameThing();
    }
}

我们看到在事务方法add中,直接调用事务方法updateStatus。从前面介绍的内容可以知道,updateStatus方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateStatus方法不会生成事务。

由此可见,在同一个类中的方法直接内部调用,会导致事务失效。

那么问题来了,如果有些场景,确实想在同一个类的某个方法中,调用它自己的另外一个方法,该怎么办呢?

3.1 新加一个Service方法

这个方法非常简单,只需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中。具体代码如下:

@Servcie
public class ServiceA {
   @Autowired
   prvate ServiceB serviceB;

   public void save(User user) {
         queryData1();
         queryData2();
         serviceB.doSave(user);
   }
 }

 @Servcie
 public class ServiceB {

    @Transactional(rollbackFor=Exception.class)
    public void doSave(User user) {
       addData1();
       updateData2();
    }

 }

3.2 在该Service类中注入自己

如果不想再新加一个Service类,在该Service类中注入自己也是一种选择。具体代码如下:

@Servcie
public class ServiceA {
   @Autowired
   prvate ServiceA serviceA;

   public void save(User user) {
         queryData1();
         queryData2();
         serviceA.doSave(user);
   }

   @Transactional(rollbackFor=Exception.class)
   public void doSave(User user) {
       addData1();
       updateData2();
    }
 }

可能有些人可能会有这样的疑问:这种做法会不会出现循环依赖问题?

答案:不会。

其实spring ioc内部的三级缓存保证了它,不会出现循环依赖问题。但有些坑,如果你想进一步了解循环依赖问题,可以看看我之前文章《
spring:我是如何解决循环依赖的?
》。

3.3 通过AopContent类

在该Service类中使用AopContext.currentProxy()获取代理对象

上面的方法2确实可以解决问题,但是代码看起来并不直观,还可以通过在该Service类中使用AOPProxy获取代理对象,实现相同的功能。具体代码如下:

@Servcie
public class ServiceA {

   public void save(User user) {
         queryData1();
         queryData2();
         ((ServiceA)AopContext.currentProxy()).doSave(user);
   }

   @Transactional(rollbackFor=Exception.class)
   public void doSave(User user) {
       addData1();
       updateData2();
    }
 }

4.未被spring管理

在我们平时开发过程中,有个细节很容易被忽略。即使用spring事务的前提是:对象要被spring管理,需要创建bean实例。

通常情况下,我们通过@Controller、@Service、@Component、@Repository等注解,可以自动实现bean实例化和依赖注入的功能。

当然创建bean实例的方法还有很多,有兴趣的小伙伴可以看看我之前写的另一篇文章《
@Autowired的这些骚操作,你都知道吗?

如果有一天,你匆匆忙忙的开发了一个Service类,但忘了加@Service注解,比如:

//@Service
public class UserService {

    @Transactional
    public void add(UserModel userModel) {
         saveData(userModel);
         updateData(userModel);
    }    
}

从上面的例子,我们可以看到UserService类没有加
@Service
注解,那么该类不会交给spring管理,所以它的add方法也不会生成事务。

5.多线程调用

在实际项目开发中,多线程的使用场景还是挺多的。如果spring事务用在多线程场景中,会有问题吗?

@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        new Thread(() -> {
            roleService.doOtherThing();
        }).start();
    }
}

@Service
public class RoleService {

    @Transactional
    public void doOtherThing() {
        System.out.println("保存role表数据");
    }
}

从上面的例子中,我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。

这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。

如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。

private static final ThreadLocal<Map<Object, Object>> resources =

  new NamedThreadLocal<>("Transactional resources");

我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

6.表不支持事务

周所周知,在mysql5之前,默认的数据库引擎是
myisam

它的好处就不用多说了:索引文件和数据文件是分开存储的,对于查多写少的单表操作,性能比innodb更好。

有些老项目中,可能还在用它。

在创建表的时候,只需要把
ENGINE
参数设置成
MyISAM
即可:

CREATE TABLE `category` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `one_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
  `two_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
  `three_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
  `four_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

myisam好用,但有个很致命的问题是:
不支持事务

如果只是单表操作还好,不会出现太大的问题。但如果需要跨多张表操作,由于其不支持事务,数据极有可能会出现不完整的情况。

此外,myisam还不支持行锁和外键。

所以在实际业务场景中,myisam使用的并不多。在mysql5以后,myisam已经逐渐退出了历史的舞台,取而代之的是innodb。

有时候我们在开发的过程中,发现某张表的事务一直都没有生效,那不一定是spring事务的锅,最好确认一下你使用的那张表,是否支持事务。

7.未开启事务

有时候,事务没有生效的根本原因是没有开启事务。

你看到这句话可能会觉得好笑。

开启事务不是一个项目中,最最最基本的功能吗?

为什么还会没有开启事务?

没错,如果项目已经搭建好了,事务功能肯定是有的。

但如果你是在搭建项目demo的时候,只有一张表,而这张表的事务没有生效。那么会是什么原因造成的呢?

当然原因有很多,但没有开启事务,这个原因极其容易被忽略。

如果你使用的是springboot项目,那么你很幸运。因为springboot通过
DataSourceTransactionManagerAutoConfiguration
类,已经默默的帮你开启了事务。

你所要做的事情很简单,只需要配置
spring.datasource
相关参数即可。

但如果你使用的还是传统的spring项目,则需要在applicationContext.xml文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。

具体配置如下信息:

   
<!-- 配置事务管理器 --> 
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 
<tx:advice id="advice" transaction-manager="transactionManager"> 
    <tx:attributes> 
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes> 
</tx:advice> 
<!-- 用切点把事务切进去 --> 
<aop:config> 
    <aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/> 
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> 
</aop:config> 

默默的说一句,如果在pointcut标签中的切入点匹配规则,配错了的话,有些类的事务也不会生效。

二 事务不回滚

1.错误的传播特性

其实,我们在使用
@Transactional
注解时,是可以指定
propagation
参数的。

该参数的作用是指定事务的传播特性,spring目前支持7种传播特性:

  • REQUIRED
    如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。
  • SUPPORTS
    如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
  • MANDATORY
    如果当前上下文中存在事务,否则抛出异常。
  • REQUIRES_NEW
    每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
  • NOT_SUPPORTED
    如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
  • NEVER
    如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
  • NESTED
    如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

如果我们在手动设置propagation参数的时候,把传播特性设置错了,比如:

@Service
public class UserService {

    @Transactional(propagation = Propagation.NEVER)
    public void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}

我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。

目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。

2.自己吞了异常

事务不会回滚,最常见的问题是:开发者在代码中手动try...catch了异常。比如:

@Slf4j
@Service
public class UserService {
    
    @Transactional
    public void add(UserModel userModel) {
        try {
            saveData(userModel);
            updateData(userModel);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

这种情况下spring事务当然不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。

如果想要spring事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则spring认为程序是正常的。

3.手动抛了别的异常

即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚。

@Slf4j
@Service
public class UserService {
    
    @Transactional
    public void add(UserModel userModel) throws Exception {
        try {
             saveData(userModel);
             updateData(userModel);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new Exception(e);
        }
    }
}

上面的这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。

因为spring事务,默认情况下只会回滚
RuntimeException
(运行时异常)和
Error
(错误),对于普通的Exception(非运行时异常),它不会回滚。

4.自定义了回滚异常

在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置
rollbackFor
参数,来完成这个功能。

但如果这个参数的值设置错了,就会引出一些莫名其妙的问题,例如:

@Slf4j
@Service
public class UserService {
    
    @Transactional(rollbackFor = BusinessException.class)
    public void add(UserModel userModel) throws Exception {
       saveData(userModel);
       updateData(userModel);
    }
}

如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常,报错的异常不属于BusinessException,所以事务也不会回滚。

即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。

这是为什么呢?

因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成:Exception或Throwable。

5.嵌套事务回滚多了

public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        roleService.doOtherThing();
    }
}

@Service
public class RoleService {

    @Transactional(propagation = Propagation.NESTED)
    public void doOtherThing() {
        System.out.println("保存role表数据");
    }
}

这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保存点。。但事实是,insertUser也回滚了。

why?

因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。

怎么样才能只回滚保存点呢?

@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {

        userMapper.insertUser(userModel);
        try {
            roleService.doOtherThing();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

可以将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。

三 其他

1 大事务问题

在使用spring事务时,有个让人非常头疼的问题,就是大事务问题。

通常情况下,我们会在方法上
@Transactional
注解,填加事务功能,比如:

@Service
public class UserService {
    
    @Autowired 
    private RoleService roleService;
    
    @Transactional
    public void add(UserModel userModel) throws Exception {
       query1();
       query2();
       query3();
       roleService.save(userModel);
       update(userModel);
    }
}


@Service
public class RoleService {
    
    @Autowired 
    private RoleService roleService;
    
    @Transactional
    public void save(UserModel userModel) throws Exception {
       query4();
       query5();
       query6();
       saveData(userModel);
    }
}


@Transactional
注解,如果被加到方法上,有个缺点就是整个方法都包含在事务当中了。

上面的这个例子中,在UserService类中,其实只有这两行才需要事务:

roleService.save(userModel);
update(userModel);

在RoleService类中,只有这一行需要事务:

saveData(userModel);

现在的这种写法,会导致所有的query方法也被包含在同一个事务当中。

如果query方法非常多,调用层级很深,而且有部分查询方法比较耗时的话,会造成整个事务非常耗时,而从造成大事务问题。

关于大事务问题的危害,可以阅读一下我的另一篇文章《
让人头痛的大事务问题到底要如何解决?
》,上面有详细的讲解。

2.编程式事务

上面聊的这些内容都是基于
@Transactional
注解的,主要说的是它的事务问题,我们把这种事务叫做:
声明式事务

其实,spring还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务,我们把这种事务叫做:
编程式事务
。例如:


   @Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
         queryData1();
         queryData2();
         transactionTemplate.execute((status) => {
            addData1();
            updateData2();
            return Boolean.TRUE;
         })
   }

在spring中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的execute方法中,就实现了事务的功能。

相较于
@Transactional
注解声明式事务,我更建议大家使用,基于
TransactionTemplate
的编程式事务。主要原因如下:

  1. 避免由于spring aop问题,导致事务失效的问题。
  2. 能够更小粒度的控制事务的范围,更直观。

建议在项目中少使用@Transactional注解开启事务。但并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用@Transactional注解开启事务开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

上周赠送了
OneThingAI 算力代金券

天翼云电脑
,这周继续给园子会员送福利。

这周赠送的是一年华为云服务器,配置是2核2G3M带宽,限量100台,先到先得,领完为止。

领取要求:博客园年度VIP或者PLUS
会员
,华为云新用户或者在华为云没有过任何消费的老用户。

领取方式:加园子
企业微信
,加好友时请备注【领华为云服务器】,或者给「博客园团队」企微发消息。

如果符合领取条件,会将您加入专门领华为云服务器的微信群,群中有一个Wod文档,按照文档中的流程完成领取,领取过程中遇到任何问题可以在群中咨询。

负责发放云服务器的是华为云服务商浙江杭云网络科技有限公司,也是园子的合作伙伴,园子的
华为云代理业务
就是和他们合作的,如果您有华为云云产品购买需求,也欢迎和我们联系。

Chrome 130 版本新特性& Chrome 130 版本发行说明

一、Chrome 130 版本浏览器更新

1. 新的桌面提示

Chrome 130 引入了一种新的 Toast 样式,用于在用户操作后提供视觉确认,或快速提供后续操作的途径。

当用户执行某个操作时,Toast 会在屏幕上短暂弹出,确认操作成功或提供快捷链接。
例如,当将某项内容添加到阅读列表时,Toast 会确认项目已添加,并提供一个快速链接以打开阅读列表侧边栏。Toast 以小型提示的形式显示,部分覆盖网页内容,部分覆盖浏览器顶部工具栏。

image

2. 用于 macOS 系统的屏幕共享平台选择器

在 macOS X Sequoia 上的 Chrome 中共享屏幕时,用户现在可以使用更新后的平台选择器选择要共享的窗口或屏幕。此新平台选择器无需为 Chrome 分配屏幕录制权限,并且与其他 macOS 应用程序中的屏幕共享一致。

新的选择器将不会在 macOS Sequoia 的第一次更新之前启用,预计 15.1 版本将在 15.0 初始版本发布后一个月内发布。在此之前,Chrome 用户可能会看到一个警告对话框,提示 Chrome 尚未使用新的选择器 API。

image

3.
新帐户菜单

一些用户现在可以通过在新标签页上点击他们的头像来访问新的账户菜单。新的账户菜单允许他们轻松地注销、切换账户以及解决与 Chrome 账户相关的错误。

3.1. IOS 上的 130

image

4.
Android 上的 PDF 查看器

此功能提供了在 Chrome 浏览器 UI 中查看 PDF 的功能。

使用此功能,PDF 将在 Chrome 中无缝呈现。用户仍然可以下载 PDF 并使用其他首选或第三方应用打开。

5.
节能模式下冻结标签

image

当节能模式激活时,Chrome 现在会冻结已隐藏且静默超过 5 分钟且占用大量 CPU 的标签。

这将延长电池寿命并通过减少 CPU 使用率来加快 Chrome 的速度。

5.1. 特殊标签除外

  1. 改标签有音频或视频会议功能
  2. 改标签控制外部设备(使用 Web USB、Web 蓝牙、Web HID 等)

6. 共享 Brotli 和共享 Zstandard 的压缩字典传输

功能增加了使用指定的先前响应作为外部字典,以 Brotli 或 Zstandard 对内容编码的响应进行压缩的支持。

7. 键盘可聚焦滚动容器

Chrome 130 通过使滚动容器在顺序焦点导航中可聚焦,改进了可访问性。目前,Tab 键不会聚焦滚动容器,除非
tabIndex
被显式设置为 0 或更大。

现在,默认情况下滚动容器可聚焦,这样无法(或不想)使用鼠标的用户可以通过 Tab 键和方向键来聚焦被裁剪的内容。只有当滚动容器中没有可通过键盘聚焦的子元素时,此行为才会启用。这样做是为了避免对包含可聚焦元素(如
<textarea>
)的滚动容器造成兼容性问题。

8. 支持非特殊的 URL

Chrome 130 支持非特殊 scheme 的 URL,例如
git://example.com/path
。此前,Chromium 的 URL 解析器不支持非特殊 URL,且将这些 URL 解析为不透明路径(opaque path),这与 URL 标准不一致。现在,Chromium 的 URL 解析器能够正确解析非特殊 URL,符合 URL 标准。

9. Android 版 Chrome 支持第三方自动填充和密码提供商

在 Chrome 130 中,添加了对 Android 自动填充的直接支持,这意味着这些提供商现在可以在 Chrome 的 Android 版本中正常工作,无需依赖无障碍 API。这将提高 Chrome 在 Android 上的性能。要利用此功能,用户需要确保在 Android 设置中配置了第三方提供商。然后,在 Chrome 中,用户需要打开设置 > 自动填充服务,并选择使用其他服务进行自动填充。

新设置将在 Chrome 130 中可用。如果用户使用新的设置,将立即生效。如果不使用新的设置,用户将继续通过无障碍功能使用 Google 和第三方提供商(如果已安装)。

10.
<meter>
元素的后备样式

在 Chrome 130 中,具有
appearance: none

<meter>
元素现在有了合理的后备样式,匹配 Safari 和 Firefox,而不是仅仅从页面上消失。此外,开发者现在可以自定义样式
<meter>
元素。

11. Chrome 浏览器中的新政策和更新政策

策略 描述
DataURLWhitespacePreservationEnabled 所有媒体类型的 DataURL 空白字符保留
CloudProfileReportingEnabled 为托管配置文件启用 Google Chrome 云报告

二、ChromeOS 130 版本更新

1. 快速插入

快速插入提供了一种快速插入表情符号、符号、GIF、Google Drive 链接以及快速计算和单位转换的方法,可以通过键盘按键(在部分型号上)或键盘快捷键实现。

在 ChromeOS 130 中,所有 ChromeOS 设备都可以使用新的快捷键 Launcher + f。新的硬件按键最初仅在三星 Galaxy Chromebook Plus 上提供,但快速插入键将在 2025 年推出,适用于更多设备。

image

2. 设置和快捷键更改

更新了设置中的快捷键和输入设备选项,包括:

  • 快速插入:Launcher + f

3. 专注于 ChromeOS

设计了“专注于 ChromeOS”功能,以帮助用户减少干扰,创建更高效的工作空间。通过“专注”,可以轻松设置和调整专注时间,启用或禁用请勿打扰(DND)模式,整理或创建新的 Google 任务,并沉浸在精选的播放列表中,这些播放列表可以帮助更好地专注,包括专注音效等。

要使用“专注”,请前往

Quick Settings > Focus

image

4. 增强的 Drive 文件访问

除了在 Tote 中标记的文件之外,还可以直接从架子上访问所有标记的 Drive 文件,现已支持离线访问。

5. Tote 中的新建议

通过本地和 Drive 文件建议,快速访问和固定最需要的文件。Tote 中的新建议部分会向用户推荐文件,提高了那些对用户有用的文件的可见性,以便他们固定并离线访问。

6. 欢迎回顾

新的欢迎回顾功能帮助用户在启动时恢复工作并探索新选项。一旦启用此功能,将能够预览并恢复上次会话中的应用和标签页。欢迎回顾还提供有用的信息,如天气、下一个日历事件、来自其他设备的最近标签页以及相关的 Google Drive 建议。

要启用此功能,请选择
Settings > System Preferences > Startup > Welcome Recap
,并确保为设备选择“
Ask every time
”。

设置 > 系统偏好 > 启动 > 欢迎回顾

image

7. 工作室风格麦克风

通过在视频通话控制中启用此功能,使 Chromebook 的内置麦克风听起来像专业的工作室麦克风。工作室风格麦克风包括现有的噪声取消和去混响效果,并通过先进的平衡、细节重建和房间适应进一步增强这些效果。启用噪声取消的用户将默认获得工作室风格麦克风的增强效果,从此版本开始。如果用户想恢复到旧的仅噪声取消效果,可以在
Settings > Device > Audio
中选择相应的选项。此功能仅适用于 Chromebook Plus 设备。

设置 > 设备 > 音频

8. AI 驱动的录音应用

ChromeOS 130 引入了新的 Google AI 驱动的录音应用,可创建转录,能够检测和标记说话者,并提供录音内容的摘要。不仅限于录音,还提供语音转文本、内容摘要和标题建议,均由 Google AI 提供支持。

9. 内容扫描用于托管访客会话

现在允许组织将 Chrome Enterprise Premium 强大的扫描和基于内容和上下文的保护扩展到 ChromeOS 上托管访客会话中的本地文件。例如,当用户尝试将包含社会安全号码的错误文件复制到外部驱动器时,该文件会立即被阻止,从而保护这一机密信息。

10. 在 Kiosk 模式中允许额外的 URL

如果 Kiosk 应用使用多个 URL 源,IT 管理员现在可以输入额外的源。所有指定的源将自动获得权限。任何不在此列表中的其他源将被拒绝权限。

image

11. 外观效果

外观效果在相机、虚拟会议和短视频产品中长期以来一直很受欢迎,并已在一些 Google 产品中推出。在 ChromeOS 130 中,将此功能集成到 Chromebook 的视频通话控制中。仅在 Chromebook Plus 设备上可用。

12. 更加可访问的隐私控制

在此次发布中,使 Chrome 浏览器的操作系统级隐私控制对用户更易获取。旨在让用户更加意识到,要使摄像头或麦克风工作,他们需要启用操作系统级的隐私控制。

image

13. 增强的键盘亮度控制

Chromebook 用户现在可以直接从设置应用轻松调整键盘亮度和控制环境光传感器。这个新功能允许将键盘亮度设置到合适的水平,并根据需要开启或关闭环境光传感器。

14. 增强的显示亮度控制

Chromebook 用户现在可以直接从设置应用轻松调整显示亮度和控制环境光传感器。这个新功能允许在设置中将屏幕亮度设置到合适的水平,并根据需要开启或关闭环境光传感器。

15. 在 ChromeOS 上帮助我阅读

在 ChromeOS 上帮助我阅读提供了一个 AI 驱动的解决方案,帮助快速找到任何文本中所需的信息。只需右键单击空白区域,即可在现有上下文菜单上方显示“帮助我阅读”卡片,轻松获取在浏览器和图库中阅读内容的要点。帮助我阅读面板展示了文本的摘要和一个自由问答字段,允许询问有关文本的具体问题。仅在 Chromebook Plus 设备上可用。

image

16. 多日历支持

新版本推出了多日历支持,允许用户查看他们在 Google 日历中选择的多个日历中的所有事件。

image

17. 画中画窗口

ChromeOS 用户现在可以享受更大的灵活性,使用画中画(PiP)窗口。PiP Tuck 允许用户将 PiP 窗口临时移动到屏幕的一侧,腾出宝贵的屏幕空间,同时保持视频的便捷访问。此外,可以通过快速双击轻松调整 PiP 窗口的大小,在两种尺寸之间切换,以获得最佳观看体验。

image

18. 改进的 ARC++ 用户体验

为了改善 ChromeOS 和 ARC++ 的用户体验,将 ARC++ 的非紧急后台和错误通知移至系统托盘。这可以防止这些消息不必要地弹出在前台,从而打扰用户的使用体验。

19. 新的策略以控制接入点名称

对于具有蜂窝功能的 Chromebook,接入点名称(APN)策略允许管理员限制自定义 APN 的使用。通过在一般网络设置中将 AllowAPNModification 标志设置为限制,他们可以防止最终用户添加或使用任何自定义 APN。

image

20. Microsoft SCEP SID 更新

仅适用于使用 Microsoft NPS 进行 RADIUS 的 SCEP 部署。如果没有将 SCEP 证书与 Microsoft NPS 结合使用于 Chromebook 网络连接,则可以忽略这些指令的其余部分。

三、Chrome 130 版本更新日期

1. Chrome 130

1.1. Beta 版

2024 年 9 月 18 日,星期三

1.2. 稳定版本

2024 年 10 月 15 日,星期二

2. Chrome操作系统

2.1. Beta 版

2024 年 10 月 1 日,星期二

2.2. 稳定版本

2024 年 10 月 29 日,星期二

参考资料