SQL别名实战:逻辑执行顺序、作用域与跨方言避坑

发布时间:2026/10/31 12:05:32
SQL别名实战:逻辑执行顺序、作用域与跨方言避坑

前阵子帮一位刚转岗的同事排查一条报表 SQL,他卡了整整一个下午,报错信息翻来覆去就一句“字段不存在”,可字段明明就躺在表里。我扫了一眼,问题出在他把列别名写进了 WHERE 条件。SQL 别名(Aliases)这玩意儿,文档上通常就两行字——“给列或表起个临时名字”——看着毫无门槛,可真正在多层嵌套、多表关联、聚合加窗口函数的复杂查询里翻车的人一点不少。它不像索引、事务那样有仪式感,却是每天写 SQL 都要碰的东西:列别名决定结果集长什么样、下游程序能不能正确取值,表别名决定多表 JOIN 时你能不能把话说清楚、不产生歧义。这篇就围绕 SQL 别名展开,从列别名和表别名的职责边界讲起,把逻辑执行顺序这个“万恶之源”掰开,再落到自连接、派生表、报表字段契约、跨方言差异这些实战场景。不管你是刚学 SQL 几个月的新手,还是天天写查询但没系统梳理过别名的老手,应该都能从里面抠出几条能直接用的规矩。

1. 别名到底在解决什么问题

1.1 别名不是语法糖,是可读性与复用性的基础设施

很多人对别名的第一印象是“省事”——表名太长,起个短名字少打几个字符。这个理解不算错,但只看到了表层。别名真正解决的是三个层面的问题,而且是层层递进的。

第一层是可读性。一条涉及七八张表的查询,如果每一处引用都写全限定名,比如sales_order_detail.unit_price * sales_order_detail.quantity,SQL 会变得像一堵墙,读的人得反复回扫才能确认哪个字段属于哪张表。给了表别名之后,d.unit_price * d.quantity一眼就能看出字段归属,视觉噪音大幅下降。第二层是复用性,也就是自连接场景——同一张表在 FROM 子句里出现两次,物理上是同一张表,逻辑上却是两个不同的角色(员工和经理、订单和退货单),不用表别名根本无法区分这两个角色。第三层是接口稳定性,列别名定义了结果集的“对外协议”,下游的报表工具、ETL 脚本、接口序列化都按这个列名取值,一旦别名变了,上游数据没错,下游全炸。

我一般会跟新人这么类比:别名就像给同事起个称呼。人还是那个人,数据还是那份数据,但你喊“老王”比喊“王建国同志”顺口,而且在同时有好几个王姓同事的时候,你得起不同的称呼才能说清楚在叫谁。称呼不改变人,但改变了沟通效率。

需要强调的是,别名不改变数据本身,它只改变“引用方式”和“输出标签”。列别名不会让price * qty真的多出一个物理列,表别名也不会复制一份表数据。理解这一点,后面所有的“为什么这里不能用别名”就都能想通了——因为别名是在查询解析和输出阶段才生效的东西,它不属于数据本身。

1.2 列别名与表别名的职责边界要分清

列别名和表别名虽然都叫“别名”,但作用域和行为完全不同,混为一谈是很多错误的源头。

列别名作用于 SELECT 的输出列表,它的生命周期从 SELECT 子句开始,主要影响结果集的列名。它可以在 ORDER BY 里被引用(大多数方言支持),在部分方言里也能在 GROUP BY 和 HAVING 里被引用,但几乎在所有方言里都不能在 WHERE 里被引用。它还可能影响CREATE VIEW的列名、INSERT ... SELECT的语义、UNION结果集的列名(取第一个 SELECT 的别名)。

表别名作用于一个查询块内部,它的生命周期覆盖 FROM、JOIN、ON、WHERE、GROUP BY、HAVING、SELECT 的整个解析过程。一旦在 FROM 里定义了表别名,同一查询块内所有对该表的引用都应该用别名,而不该再写全表名——虽然部分方言允许混用,但混用是歧义的温床。表别名还会被相关子查询“继承”,内层查询可以引用外层定义的表别名,这就是相关子查询能工作的基础。

用一个表格把两者的差异摆清楚:

维度列别名表别名
定义位置SELECT 列表FROM / JOIN 子句
影响对象结果集列名查询块内对表的引用方式
WHERE 中可用基本不可用可用
ORDER BY 中可用多数方言可用可用(通过列引用)
是否影响数据否否
典型误用在 WHERE 里引用别名重复、定义后仍写全表名

把这张表记在心里,后面遇到“为什么这里报字段不存在”,你第一反应就应该是去检查别名的类型和作用域,而不是怀疑表结构。

1.3 为什么别名规则在不同数据库里长得不一样

标准 SQL 对别名的规定其实是比较克制的:列别名用AS定义,表别名也用AS定义(表别名甚至可以省略 AS),但对“别名能在哪些子句里被引用”并没有全部强制统一,这就给了各数据库方言自由发挥的空间。

于是你会看到一堆看似矛盾的现象:MySQL 允许你在 GROUP BY 里写列别名,SQL Server 直接报错;PostgreSQL 在 ORDER BY 里优先匹配输出列名,而在 HAVING 里又不认别名;Oracle 对表别名基本不接受AS关键字,但对列别名的AS又很宽容。这些差异不是谁对谁错,而是各家在“解析顺序”和“名字解析优先级”上的实现策略不同。

注意:跨数据库迁移 SQL 时,别名是最容易被忽略的兼容性雷区之一。一段在 MySQL 里跑得好好的查询,换到 SQL Server 可能就因为 GROUP BY 用了列别名而直接罢工。

我的建议是:写 SQL 时尽量往标准靠,别贪图方言给的便利。能用子查询包一层解决的,就别依赖“某个方言恰好允许在 GROUP BY 里用别名”这种特性。你省下的那几行代码,将来迁移的时候会加倍还回来。

2. 列别名:写法、执行顺序与那些反直觉的报错

2.1 三种写法怎么选:AS、省略 AS、引号包裹

列别名有三种常见写法,先看例子:

-- 写法一:标准写法,带 AS SELECT unit_price * quantity AS amount FROM order_detail; -- 写法二:省略 AS SELECT unit_price * quantity amount FROM order_detail; -- 写法三:带引号,用于特殊字符或保留字 SELECT unit_price * quantity AS "订单金额" FROM order_detail;

带 AS 的写法是我最推荐的,也是标准 SQL 的建议形式。多打两个字符,换来的是“这一坨是别名”的明确信号,尤其在 SELECT 列表很长、表达式很复杂的时候,AS 就是视觉分隔符。省略 AS 的写法在 MySQL、PostgreSQL、SQL Server 里都能跑,但可读性差,而且当表达式后面紧跟的是保留字时容易解析歧义,我不建议在团队协作的代码里用。

引号包裹是必须掌握的,因为有些别名不带引号根本写不出来。别名里含空格、含连字符、以数字开头、和保留字撞名、用中文,这些情况都必须加引号。但引号的形式各家不同:MySQL 默认用反引号,也可以在开启 ANSI_QUOTES 模式后用双引号;PostgreSQL 和 Oracle 用双引号;SQL Server 用方括号或双引号。写跨库脚本时,双引号是相对通用的选择,但前提是对应数据库的引号模式配置正确。

这里有个非常容易被忽略的坑:双引号包裹的别名会保留大小写。在 PostgreSQL 里,SELECT 1 AS Foo返回的列名会被折叠成foo,而SELECT 1 AS "Foo"返回的列名就是Foo。这个差异在手工查数据时无所谓,但在程序里按列名取值时就是致命的——Java 的 JDBC 用rs.getInt("Foo")取不到foo列,ORM 映射也会失败。我见过一次线上问题,排查了两个小时,最后发现是某位同事在写视图时给列别名加了双引号,导致大小写和接口约定不一致。

提示:给下游程序消费的结果集,列别名一律用小写字母加下划线,不加引号,不用中文,不用空格。这条规矩能省掉 90% 的取值失败问题。

2.2 WHERE 里为什么不能用列别名——逻辑执行顺序是根源

这是别名话题里最高频的疑问,也是开头那位同事卡住一下午的原因。先看一段会报错的 SQL:

SELECT unit_price * quantity AS amount FROM order_detail WHERE amount > 100;

在 MySQL 里报Unknown column 'amount' in 'where clause',在 PostgreSQL 里报column "amount" does not exist,在 SQL Server 里报Invalid column name 'amount'。三家报错措辞不同,本质是同一个:WHERE 执行的时候,amount这个别名还不存在。

要理解这一点,必须记住 SQL 的逻辑执行顺序,它和我们书写的顺序完全不一样:

执行阶段子句说明
1FROM / JOIN确定数据源,展开表别名
2ON连接条件过滤
3WHERE行级过滤
4GROUP BY分组
5HAVING组级过滤
6SELECT计算输出表达式,此时列别名才诞生
7DISTINCT去重
8ORDER BY排序
9LIMIT / OFFSET截断

看清楚了:SELECT 在第 6 步,WHERE 在第 3 步。列别名是在 SELECT 阶段才被定义的,WHERE 阶段它压根还没出生,数据库自然找不到。这不是哪个数据库的实现缺陷,而是关系模型和 SQL 语义决定的。

那想在 WHERE 里用计算结果过滤怎么办?两条路。第一条是重复写表达式:

SELECT unit_price * quantity AS amount FROM order_detail WHERE unit_price * quantity > 100;

这条能跑,但表达式一复杂就很丑,而且改一处要改两处,容易漏。第二条是包一层子查询或 CTE,让别名在下游可见:

SELECT * FROM ( SELECT unit_price * quantity AS amount FROM order_detail ) t WHERE t.amount > 100;

我更推荐第二种,原因不只是可读性。当表达式涉及聚合或者窗口函数时,重复写表达式可能带来重复计算,包一层能让优化器更清楚地看到你的意图。当然,包一层也不是没有代价,某些数据库对派生表的条件下推优化并不总是完美,极端情况下性能会有差异,这需要结合执行计划判断。

2.3 ORDER BY、GROUP BY、HAVING 里的别名行为差异

按执行顺序推,ORDER BY 在第 8 步,晚于 SELECT,所以它能看到列别名,这符合直觉。GROUP BY 在第 4 步,早于 SELECT,按理说不该看到列别名,但 MySQL 和 PostgreSQL 都做了额外支持。HAVING 在第 5 步,同样早于 SELECT,支持情况就更参差了。

先看 ORDER BY,这是最稳的:

SELECT unit_price * quantity AS amount FROM order_detail ORDER BY amount DESC;

主流方言都支持。但这里藏着一个歧义陷阱:如果别名和表里已有的列名重名,ORDER BY 到底按哪个排?PostgreSQL 的规则是优先匹配输出列名,MySQL 也倾向于此。也就是说,如果你写SELECT create_time AS id FROM t ORDER BY id,排的可能不是你预期的那个id。这种“别名遮蔽原列名”的行为很容易让人误判结果,我的做法是别名尽量不和真实列名重名,实在要重名就在 ORDER BY 里写完整表达式。

再看 GROUP BY,差异就出来了:

数据库GROUP BY 能否用列别名备注
MySQL可以但有歧义时会优先按输入列解析
PostgreSQL可以要求别名无歧义
SQL Server不可以需重复表达式
Oracle不可以需重复表达式

最后看 HAVING,支持面更窄:

数据库HAVING 能否用列别名备注
MySQL可以宽松解析
PostgreSQL不可以需写聚合表达式
SQL Server不可以需写聚合表达式
Oracle不可以需写聚合表达式

我给自己的规矩很简单:GROUP BY 和 HAVING 里永远不写列别名,老老实实写表达式。原因有两个,一是跨库兼容,二是避免歧义——当你在一堆聚合里看到HAVING total > 100,你得回头看total到底是哪个表达式的别名,而写HAVING SUM(amount) > 100是一眼就懂的。省下的那几个字符,不值得用可维护性去换。

3. 表别名:从自连接到多表 JOIN 的命名纪律

3.1 自连接场景:不用表别名根本写不出来

自连接是表别名最不可替代的场景。所谓自连接,就是把同一张表在 FROM 里引用两次,让它在逻辑上扮演两个角色。最经典的例子是员工表里的上下级关系:

SELECT e.name AS employee_name, m.name AS manager_name FROM employee e JOIN employee m ON e.manager_id = m.id WHERE e.salary > m.salary;

这段查询找的是“工资比直属上级高的员工”。employee表出现了两次,一次叫e(员工视角),一次叫m(经理视角)。如果不给表别名,e.name和m.name根本无法区分,数据库也不知道manager_id该关联到哪一次引用。这不是“要不要用”的问题,是“不用就写不出来”的问题。

这里有个细节值得说:AS关键字在表别名里是可选的,而且很多团队的习惯是省略——FROM employee e比FROM employee AS e更常见。这跟列别名推荐带 AS 的建议看似矛盾,其实不矛盾:列别名带 AS 是为了在长 SELECT 列表里划清边界,表别名在 FROM 里位置固定,省略 AS 反而更清爽。团队内部统一就行,别一半带一半不带。

自连接还有一个容易踩的坑:连接条件写错方向。e.manager_id = m.id和e.id = m.manager_id是两个完全相反的语义,前者是“e 的上级是 m”,后者是“m 的上级是 e”。别名一多,方向就容易搞混。我的习惯是给别名起有意义的名字(e代表 employee,m代表 manager),而不是a、b,这样写条件的时候脑子里有画面,出错率会低很多。

3.2 多表 JOIN 的别名命名规范

表一多,别名命名就成了团队协作的隐形契约。我见过最糟糕的情况是一条查询里出现了a、b、c、d、e五个别名,读的人要不停往上翻才知道c是哪张表。这种代码自己写的时候爽,交接的时候就是灾难。

我的命名原则是取表名的有意义的缩写,保持全局一致:

表名推荐别名不推荐
usersua、t1
ordersob、t2
order_detailodc、t3
productspd、t4
user_addressuae、t5

规则可以总结成几条:单表用首字母或前两个字母;组合词用各单词首字母(order_detail→od);别名冲突时加数字后缀但要克制;永远不用a、b、c这种无语义的字母;别名一旦定义,整条查询里所有引用都必须用别名,不要这里写o.status、那里写orders.status。

还有一条经常被忽略:别名要有作用域意识。表别名只在它所在的查询块内有效,子查询里定义的别名不会泄漏到外层,外层别名在内层可以被引用(相关子查询),但如果内层定义了同名别名,就会发生遮蔽。写多层嵌套时,我习惯给不同层级的别名加不同前缀,比如外层用o、内层用od,避免视觉混淆。

3.3 派生表、CTE 与子查询的别名约束

派生表(FROM 子句里的子查询)的别名规则,各家差异很大,而且报错信息往往不够友好。

MySQL 和 PostgreSQL 都要求派生表必须有别名,PostgreSQL 的报错是subquery in FROM must have an alias,MySQL 的报错是Every derived table must have its own alias。SQL Server 同样要求别名。也就是说,你写FROM (SELECT ...)而不给别名,在主流数据库里基本都会失败。

-- 会报错:派生表缺少别名 SELECT * FROM (SELECT id, name FROM users); -- 正确写法 SELECT * FROM (SELECT id, name FROM users) t;

CTE(公用表表达式)的别名就是它自己的名字,规则相对简单:

WITH active_users AS ( SELECT id, name FROM users WHERE status = 1 ) SELECT * FROM active_users;

这里的active_users既是 CTE 的名字,也是后续引用的表别名。CTE 的好处是可读性,尤其是当一个复杂子查询被多次引用时,用 CTE 抽出来比复制粘贴强太多。但要注意,不同数据库对 CTE 的优化策略不同,有些版本会把 CTE 物化,有些会内联展开,性能表现可能和直接写子查询有差异,这需要看执行计划验证,不能想当然。

相关子查询里的别名作用域是另一个容易绕晕的点:

SELECT o.id FROM orders o WHERE EXISTS ( SELECT 1 FROM order_detail od WHERE od.order_id = o.id );

内层子查询用到了外层的o.id,这是合法的,因为外层别名对内层可见。但如果内层也定义了o这个别名,就会遮蔽外层,导致条件关联到错误的表。这种 bug 极其隐蔽,因为 SQL 能正常执行,只是结果不对。我的做法是内层别名永远不要和外层重名,宁可多打几个字符。

4. 别名在真实业务查询中的落地打法

4.1 报表查询的列别名就是接口契约

做报表和做接口的人应该有一个共识:结果集的列名是契约,不是装饰。一旦下游按某个列名取值,这个列名就不能随便改。我见过太多因为改列别名导致下游报表空白的事故,排查起来极其费劲,因为数据库不报错,数据也正常返回,只是字段名变了。

所以我在写报表 SQL 时,会给列别名定几条硬规矩。第一,英文小写加下划线,不用中文、不用空格、不用驼峰。中文别名虽然 MySQL 和 PostgreSQL 都支持,但对程序极不友好,编码问题、转义问题层出不穷。第二,不用数据库保留字,比如order、group、desc、key,用了就得加引号,加了引号又引出大小写问题,纯属给自己找麻烦。第三,别名要语义明确但不能太长,total_amount比amt清楚,比the_total_amount_of_all_orders好维护。

还有一条经验:聚合列的别名要体现聚合语义。SUM(amount) AS total_amount、COUNT(*) AS order_count、AVG(price) AS avg_price,这些命名让读的人不用回看表达式就知道这一列是什么。如果写成SUM(amount) AS amount,乍一看还以为原始明细列,容易误用。

4.2 别名与聚合、窗口函数配合时的边界

聚合函数和窗口函数是别名问题的高发区。先看聚合:

SELECT user_id, SUM(amount) AS total_amount FROM orders GROUP BY user_id HAVING SUM(amount) > 1000;

这里 HAVING 里我没用total_amount这个别名,而是重复写了SUM(amount)。原因前面说过,跨方言兼容性。虽然 MySQL 能跑HAVING total_amount > 1000,但换到别的库就废了。多写几个字符换来可移植性,我认为值。

窗口函数的情况更微妙。窗口函数是在 SELECT 阶段计算的,和列别名诞生的阶段是同一层,所以同一层 SELECT 里不能引用本层定义的别名:

-- 会报错:不能在同一层引用别名 SELECT user_id, SUM(amount) AS total_amount, RANK() OVER (ORDER BY total_amount DESC) AS rnk FROM orders;

这段在多数数据库里会报错,因为total_amount在窗口函数的 ORDER BY 里还不存在。正确做法是包一层子查询:

SELECT user_id, total_amount, RANK() OVER (ORDER BY total_amount DESC) AS rnk FROM ( SELECT user_id, SUM(amount) AS total_amount FROM orders GROUP BY user_id ) t;

这个“同层不能互引”的规则同样适用于 SELECT 列表内部:SELECT a + b AS x, x * 2 AS y这种写法在主流数据库里都会失败,因为x在计算y的时候还没定义。别指望数据库帮你做链式推导,它只认已经存在的列。

4.3 别名涉及外部输入时的处理原则

有一种场景需要格外小心:别名来自外部输入。比如某些管理后台允许用户自定义报表列名,或者某些动态查询拼接工具会把用户输入的字段名直接拼进 SQL 当别名。这里的原则很明确——标识符不能参数化,只能走白名单校验。

参数化查询(预编译)能防住值注入,但防不住标识符注入,因为表名、列名、别名在 SQL 解析阶段就要确定,不能作为参数传入。所以当别名需要动态生成时,正确做法是维护一个允许的别名白名单,用户输入只作为 key 去查表,查到了才拼进 SQL,查不到就拒绝。绝不能把用户输入直接字符串拼接成别名。

这块我不想展开太多细节,只强调结论:凡是拼进 SQL 结构部分(表名、列名、别名、排序方向)的内容,一律白名单,不要相信任何“我们会做过滤”的承诺。这是写数据访问层的基本功。

5. 高频报错与跨方言差异速查

5.1 典型报错与定位路径

我把这些年遇到的别名相关报错整理成了一张速查表,按报错信息反查原因,比从头读 SQL 快得多。

报错信息(大意)常见原因定位与解决
Unknown column 'xxx' in 'where clause'在 WHERE 里引用了列别名改写成表达式,或用子查询包一层
Column 'xxx' in field list is ambiguous多表 JOIN 后列名冲突且未加表别名限定给列加表别名前缀
Not unique table/alias: 'xxx'同一查询块内表别名重复检查 FROM/JOIN 里的别名定义
Every derived table must have its own alias派生表缺少别名给 FROM 后的子查询加别名
subquery in FROM must have an alias同上,PostgreSQL 版本同上
Invalid column name 'xxx'SQL Server 在 WHERE/GROUP BY 用了别名改写表达式
column reference "xxx" is ambiguousPostgreSQL 列引用歧义加表别名限定

定位思路可以总结成三步:先看报错位置是哪个子句,判断是“别名用错了地方”还是“别名没定义”;再检查别名的作用域,看它是否在可见范围内;最后检查是否有重名遮蔽,尤其是多层嵌套的时候。按这个顺序走,大部分问题五分钟内能定位。

5.2 跨方言差异对照表

下面这张表是我自己整理的,覆盖了日常最常遇到的几种方言。表里的“支持”指的是“可以直接写列别名而不报错”,具体行为还受版本和配置影响,使用前最好在目标库上实测一下。

能力MySQLPostgreSQLSQL ServerOracle
列别名省略 AS支持支持支持支持
表别名使用 AS支持支持支持不支持
WHERE 引用列别名不支持不支持不支持不支持
GROUP BY 引用列别名支持支持不支持不支持
HAVING 引用列别名支持不支持不支持不支持
ORDER BY 引用列别名支持支持支持支持
派生表强制别名是是是否
别名引号形式反引号/双引号双引号方括号/双引号双引号
未加引号别名大小写保留折叠为小写保留折叠为大写

这张表里最值得记的是两行:WHERE 全都不支持,这个没有例外;别名大小写处理各家不同,这个直接影响程序取值。其他的按需查就行,不用死记。

注意:Oracle 对表别名的AS关键字不支持,写FROM employee AS e会直接语法报错,得写成FROM employee e。从 MySQL 迁 SQL 到 Oracle 的时候,这个是最容易被漏掉的批量问题。

6. 我踩过的坑与几条硬规矩

6.1 三个真实翻车场景的复盘

第一个坑是别名大小写导致接口取值失败。当时做一个数据导出功能,SQL 里写了SELECT user_name AS "UserName",本地测试没问题,因为测试脚本用的是不区分大小写的方式取值。上了生产之后,下游服务用严格模式按列名取,直接拿不到数据,接口返回一堆空值。排查的时候先怀疑数据权限,再怀疑字符集,最后才想到去看结果集元数据里的真实列名。教训就是前面说的那条:给程序消费的结果集,别名一律小写加下划线,不加引号。

第二个坑是ORDER BY 别名遮蔽原列名。有过一条查询,本意是按创建时间倒序,结果写成了ORDER BY id,而 SELECT 里恰好有一个xxx_id AS id的别名,导致排序按的是那个业务 ID 而不是主键。数据量小的时候看不出来,数据量一大,分页结果就乱了。后来我养成了一个习惯:别名尽量不和真实列名重名,实在要重名,ORDER BY 里就写完整表达式或者明确加表前缀。

第三个坑是多层子查询别名遮蔽。一条统计查询里,外层用了o代表订单表,内层子查询里也用了o代表另一张表,结果关联条件静默地关联到了错误的表上。SQL 不报错,数据也能出来,只是数字对不上。这种问题最难查,因为你的第一反应永远是怀疑业务逻辑,而不是怀疑别名。后来我给自己定了规矩:嵌套查询里,每一层的别名前缀必须不同,外层o、内层od、再内层p,宁可名字长一点,也不要出现同名遮蔽。

6.2 可以直接抄走的五条别名规矩

写到最后,把我自己一直在用的几条规矩列出来,都是踩坑换来的,可以直接抄。

第一条,列别名带 AS,表别名可省 AS,但团队内必须统一。混着写不会报错,但读代码的人会分心。

第二条,WHERE 里永远不写列别名。需要过滤计算结果的,用子查询或 CTE 包一层,这是唯一稳妥的写法,跨库通用。

第三条,GROUP BY 和 HAVING 里也不写列别名。虽然部分方言支持,但兼容性和可读性双重考虑,写表达式更划算。

第四条,别名命名要有语义,不用 a、b、c。多表 JOIN 用表名缩写,自连接用角色名(employee/manager),嵌套查询用不同前缀区分层级。

第五条,给程序消费的列别名,小写加下划线,不加引号,不用保留字,不用中文。这一条能挡掉大部分“数据明明有但程序取不到”的诡异问题。

别名这个知识点,单看每一条都不难,难的是在复杂查询里保持一致的纪律。我个人的体会是,写 SQL 的时候多花十秒钟想清楚“这个别名在哪个阶段可见、会不会和谁重名”,能省掉后面半小时的排查。尤其是接手别人代码或者做跨库迁移的时候,别名往往是最先暴露问题的地方,提前把规矩立好,比事后救火轻松得多。