系统函数不处理数据,只回答「我在哪儿」:当前库、登录名、客户端、版本。
改数据之前先跑一句 SELECT DB_NAME(),确认没连错库——这一句能省掉很多事故。
现代查元数据用 sys 视图,比 OBJECT_ID、COL_LENGTH 这些函数清楚得多。
系统函数是「元数据」这一层的东西——它们查的不是业务数据,是数据库自己的信息。
教材 7.5 分三小节:常用系统函数、元数据函数、系统函数在排查里的用法。
前两节是清单,第三节才是重点——它讲的是「怎么用这些函数定位问题」。
演示里跑不出 SQL Server 那套(DB_NAME()、HOST_NAME() 都没有),
但 SQLite 有等价的:sqlite_version()、PRAGMA database_list、sqlite_master。
思路是一样的:先确认「我在哪个库、有哪些对象」,再动手。
一、常用的七个
| 函数 | 返回什么 | 什么时候用 |
|---|---|---|
@@VERSION |
版本字符串 | 查兼容性 |
DB_NAME() |
当前库名 | 改数据前确认 |
HOST_NAME() |
客户端机器名 | 查是谁连的 |
SUSER_NAME() |
当前登录名 | 查权限问题 |
OBJECT_ID('表') |
对象内部编号 | 判断对象在不在 |
NEWID() |
生成 GUID | 要全局唯一值时 |
ISNULL(x,y) |
空值替换 | 报表里填空 |
DB_NAME() 是最该养成习惯的一个。 连着生产库和测试库的时候,
敲 DELETE 之前先看一眼这个——这一眼能省掉的事故,比任何规范都实在。
OBJECT_ID('表名') 返回 NULL 表示表不存在。
用它做「建表前先判断」比 IF EXISTS (SELECT * FROM sys.tables ...) 短。
NEWID() 生成的是 GUID,全局唯一但不是递增的。所以拿它当主键会让插入变慢——
新行随机分布在索引里,每次都造成页分裂。要唯一又要递增,用「NEWSEQUENTIALID()」或者自增列。
二、元数据函数
| 函数 | 返回什么 | 注意 |
|---|---|---|
COL_LENGTH('表','列') |
列占多少字节 | NULL 表示列不存在 |
COL_NAME(id, 列号) |
列名 | 配合 syscolumns 用 |
OBJECTPROPERTY(id,'IsTable') |
是不是表 | 1 是,0 不是 |
SCHEMA_NAME(id) |
架构名 | 一般是 dbo |
这四个现在用得少了,因为有了 sys 视图。查表用 sys.tables、查列用 sys.columns、
查对象用 sys.objects——比这些函数清楚,也能一次查出一批。
但老代码里这些函数到处都是。 看到 COL_LENGTH 返回 NULL,
就该想到「列不存在」——这个直觉能省很多时间。
本课后面讲高级应用(第 10 到 17 章)时会用 sys 视图。那时候再细说——
现在只要知道「有这些东西、它们干什么用」。
三、排查里的用法:四行一句话
这是本节最实用的一段。 出问题时,先把这四行发过去:
SELECT DB_NAME() AS 当前库,
SUSER_NAME() AS 登录名,
HOST_NAME() AS 客户端,
@@VERSION AS 版本
比说「我连不上」有用得多。 对方一眼能看出:是不是连错库了、是不是权限不对、
是不是版本不兼容——这三个是最常见的原因。
建议把这四行抄进自己的常用脚本。 比背函数有用,
而且每次出问题都能用上——这是投入产出比最高的一句 SQL。
HOST_NAME() 在查「谁在锁表」的时候特别好用。查到锁的来源进程号,再用 HOST_NAME() 一对照,
就知道是哪台机器——比在服务器上翻连接列表快。
四、动手 Demo:查「我在哪儿、我是谁」
这一段先列 SQL Server 的七个系统函数(本沙箱不执行),再用 SQLite 的等价物真跑一遍:版本号、 挂着的库、表数量——思路完全一样。
import sqlite3
c = sqlite3.connect(u":memory:")
print(u"版本:", c.execute(u"SELECT sqlite_version()").fetchone()[0])
print(u"挂着的库:")
for r in c.execute(u"PRAGMA database_list"):
print(u" ", r[1], r[2] or u"(临时库)")
n = c.execute(u"SELECT COUNT(*) FROM sqlite_master "
u"WHERE type = 'table'").fetchone()[0]
print(u"表数量:", n)
实跑输出:
--------------------------------------------------------------------
系统函数:排查时的第一把工具
--------------------------------------------------------------------
① SQL Server 的常用系统函数
--------------------------------------------------------------------
| 函数 | 返回什么 | 什么时候用 |
|---|---|---|
| @@VERSION | 版本字符串 | 查兼容性 |
| DB_NAME() | 当前库名 | 确认连的哪个库 |
| HOST_NAME() | 客户端机器名 | 查是谁连的 |
| SUSER_NAME() | 当前登录名 | 查权限问题 |
| OBJECT_ID('表') | 对象内部编号 | 判断对象在不在 |
| NEWID() | 生成 GUID | 要全局唯一值时 |
| ISNULL(x,y) | 空值替换 | 报表里填空 |
这几个函数的价值在「排查」——连不上、找不到表、权限不对,先用它们确认「我在哪儿」。
DB_NAME() 最常用:改数据之前先跑一句 SELECT DB_NAME(),确认没连错库。
这一句能省掉的事故,比任何规范都实在。
② 本沙箱的等价物
--------------------------------------------------------------------
sqlite_version() = 3.53.1
当前挂着的库:
main -> (临时库)
sqlite_master 里的表数:0
SQLite 没有 DB_NAME(),但 PRAGMA database_list 能列出所有挂着的库。
SQLite 也没有 OBJECT_ID(),用 sqlite_master 查——那张表记着所有表、索引、视图。
思路是一样的:先确认「我在哪个库、有哪些对象」,再动手。
③ 元数据函数:查表结构的三种办法
--------------------------------------------------------------------
| 函数 | 返回什么 | 注意 |
|---|---|---|
| COL_LENGTH('表','列') | 列占多少字节 | 0 表示列不存在 |
| COL_NAME(id, 列号) | 列名 | 配合 syscolumns 用 |
| OBJECTPROPERTY(id,'IsTable') | 是不是表 | 1 是,0 不是 |
现代写法是查系统视图:sys.tables、sys.columns、sys.objects,比这些函数清楚多了。
但老代码里这些函数到处都是,看懂就行——看到「COL_LENGTH 返回 0」,就知道是列不存在。
本课后面第 10 到 17 章讲高级应用时,会用 sys 视图查表结构,那时候再细说。
④ 一个实用的排查组合
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| SELECT DB_NAME() AS 当前库,
| SUSER_NAME() AS 登录名,
| HOST_NAME() AS 客户端,
| @@VERSION AS 版本
四行一句,把「我在哪个库、我是谁、从哪来、什么版本」全查出来。
出问题时先把这四行发过去,比说「我连不上」有用得多——对方一眼能看出是不是环境问题。
这一句建议抄进自己的常用脚本里,比背函数有用。
这三段做的事跟 SQL Server 那四行一样:确认环境。版本、库、
对象数量——先知道自己在哪儿,再动手。
PRAGMA database_list 在 SQLite 里相当于「列出所有数据库」。
它比 SQL Server 的 sys.databases 简单——只列当前连接挂着的,不是实例上全部的。
sqlite_master 就是 SQLite 的「系统表」。 它记着所有表、索引、视图、
触发器——相当于 SQL Server 的 sys.objects。
所以「查元数据」这件事,各家都有,只是名字不同。SQL Server 用 sys 视图,
SQLite 用 sqlite_master,MySQL 用 information_schema——思路一样,写法全不一样。
30 秒实验
- 在你的库上跑
SELECT DB_NAME(), SUSER_NAME(), HOST_NAME()。 把这三个值记下来——以后每次改数据前先看一眼,养成习惯。 - 跑
SELECT OBJECT_ID('不存在的表'),看它返回什么。 返回NULL——这就是「判断对象存不存在」的写法。 - 想一想:为什么
NEWID()不适合当主键? 因为它是随机分布的。 新行插在索引中间, 每次都造成页分裂——而自增列总是插在最后,不会有这个问题。
常见误区
- 「连着生产库和测试库,靠记忆区分就行」。记忆会错。
改数据前跑一句
SELECT DB_NAME(),两秒钟的事。这一步省不掉——省掉的人后来都补过课。 - 「
NEWID()生成的 GUID 当主键最好,永远不重复」。唯一是真的, 但它是随机分布,插入会让索引频繁分裂。数据量大了之后,插入性能会明显下降。 - 「查元数据用
OBJECT_ID、COL_LENGTH这些函数最直接」。现代写法是查sys视图。 函数只能一次查一个对象,视图能一次列出一批——而且条件、排序、连接都能用。
自测三问(点开对答案)
1. `DB_NAME()` 有什么用?为什么说它最重要? —— **返回当前连的库名。** 改数据前跑一句, 能确认没连错库——**连着生产和测试的时候,这一句能省掉删错库的事故。** 2. `OBJECT_ID('表名')` 返回 `NULL` 说明什么? —— **说明这个对象不存在。** 所以可以拿它做「建表前先判断」——比查 `sys.tables` 短, 但只能一次判断一个。 3. 为什么 `NEWID()` 不适合当主键? —— **因为它是随机分布的。** 新行插在索引中间, 每次都要分裂页——**自增列总是插在最后,不会有这个问题。** 要唯一又要递增, 用 `NEWSEQUENTIALID()`。下一篇 7.6 讲自定义函数,也是第 7 章的收尾。 标量函数返回一个值,表值函数返回一张表——后者性能好得多,因为它能被优化器展开进外层查询。
- ☐ (本篇完,下一篇 7.6 自定义函数)