🦏 第2章 SQL语法
📄 一句话概括

MySQL 的类型是「关卡」,这里的类型是「标签」。 教材把类型分四组:数值、字符串、日期时间、 二进制;而本沙箱只认五种存储类,长度和格式都不强制。 Demo 撞出三件事:16 个字符塞进 VARCHAR(10) 照收(这里不拦,MySQL 报 Data too long); DECIMAL(8,2) 存 0.1 加 0.2 得 0.30000000000000004(这里是浮点,MySQL 是定点); 日期 2026/04/02 也照收(这里当字符串存)。

📝 本章和前面几章的关系

上一篇讲语法, 这一篇讲每个字段该挂什么类型——它决定三件事:能存什么、占多少空间、算出来准不准。 也是全课差异最大的一篇:MySQL 有二十多种类型,SQLite 只有五种存储类, 所以这一篇的每个结论都要连引擎一起说。

一、数值:整数分宽度,小数分定点与浮点

整数五种宽度:TINYINT(1 字节,-128~127)、SMALLINT(2 字节)、MEDIUMINT(3 字节)、 INT(4 字节)、BIGINT(8 字节)。 按取值范围挑最小的那个,索引更小、更快。 UNSIGNED 去掉负数那一半,正数上限翻倍; ZEROFILL 补前导零(INT(4) ZEROFILL 存 7 显示 0007)—— 这个只在显示时起作用,8.0 已不推荐。 小数分两种,这一点必须分清:DECIMAL(p,s) 是定点数,p 是总位数、s 是小数位, 存 0.1 就是 0.1,算钱必须用它;FLOAT/DOUBLE 是二进制浮点,快但算不准。 DECIMAL(8,2) 的意思:一共 8 位数字、其中 2 位是小数, 所以整数部分最多 6 位,最大 999999.99。

二、字符串:定长与变长,差在「补不补空格」

CHAR(n) 定长:存 4 个字符的 AB 会补两个空格,取出来时又自动删掉, 所以永远读不到尾随空格。 适合长度固定的(性别、省份代码、身份证)。 VARCHAR(n) 变长:按实际长度存,多一个字节记长度。 n 是「字符数」不是字节数——utf8mb4 下一个汉字算一个字符, 但它占 4 个字节,所以 VARCHAR(255) 最坏要 1020 字节。 TEXT/LONGTEXT 放长文本, 不能有默认值、索引只能前缀索引。 ENUM 是枚举(只能取列出的几个值)、 SET 是集合(可多选)。 选型口诀: 长度固定用 CHAR,其余用 VARCHAR,超过几千字用 TEXT。 别一律 VARCHAR(255)——它占索引空间, 也掩盖了「这个字段到底多长」的信息。

三、日期时间:四种类型,一个 2038 的坑

DATE 只存日期(2026-04-10)、TIME 只存时间(10:30:00)、 DATETIME 存日期加时间(8 字节,不随时区变)、TIMESTAMP 也存日期加时间(4 字节, 存的是 UTC,取出来按会话时区换算)。 TIMESTAMP 的范围只到 2038-01-19(4 字节有符号整数装不下更大的秒数), 这就是著名的「2038 问题」。 长期存时间用 DATETIME,别用 TIMESTAMP。 YEAR 只存年份(1 字节)。 8.0 起 DATETIME 可以带小数秒:DATETIME(3) 精确到毫秒。

四、二进制:放文件的那几种

BINARY(n)/VARBINARY(n) 是定长/变长的二进制串, BLOB 系列(TINYBLOB/BLOB/MEDIUMBLOB/LONGBLOB)放图片、文件。 实务上不建议把图片塞数据库:备份变大、查询变慢、连接被占。 常规做法是文件存磁盘或对象存储,库里只存路径。 什么时候真该用:数据量和库一起走、要事务一致(比如电子签章的原文哈希)、 文件小且必须原子写入。 除此之外都存路径。

五、本沙箱的类型:五种存储类,长度不强制

SQLite 只有五种存储类:NULL、INTEGER、REAL、TEXT、BLOB。 VARCHAR(10) 里的 10 只是「亲和性提示」,不拦长度。 Demo 实测两件事:16 个字符塞进 VARCHAR(10) 照收; DECIMAL(8,2) 存成了 real,0.1 + 0.2 得 0.30000000000000004。 所以本课的建表语句照 MySQL 的类型写(这样你搬到真库上不用改), 但要知道这里只是「认这个名字」,不照它执行。 要算准钱,先乘 100 转整数—— Demo 最后一段就是这么做的。

六、小结

教材小结:数据类型分数值、字符串、日期时间、二进制四组;整数按范围挑宽度,钱用 DECIMAL, 字符串分 CHAR 与 VARCHAR,时间分 DATE/TIME/DATETIME/TIMESTAMP。 本课补三条:一是长度和类型在这里不强制,MySQL 会拦; 二是 DECIMAL 在这里退化成浮点,算钱要先转整数;三是日期在这里是字符串,格式靠自己保证。

七、动手 Demo:五种类型各撞一次墙

Demo 六段:按 MySQL 写法建一张五种类型的表,然后依次撞:超长字符串、小数精度、非标准日期格式, 最后给一张 MySQL 与 SQLite 的类型对照表。 这一篇的每一段都在演示「这里为什么不拦你」——拦不拦是引擎的差别,该定什么类型是你的决定。

import sqlite3
c = sqlite3.connect(":memory:")
c.execute("""CREATE TABLE t_types (
    id INTEGER PRIMARY KEY,
    code VARCHAR(10),
    flag CHAR(4),
    price DECIMAL(8,2),
    made_on DATE)""")

# 16 个字符塞进 VARCHAR(10)
c.execute("INSERT INTO t_types (code, flag, price, made_on) "
          "VALUES ('ABCDEFGHIJKLMNOP', 'AB', 9.90, '2026-04-01')")
c.execute("SELECT code, length(code) FROM t_types")
# 日期格式随便给也收
c.execute("INSERT INTO t_types (code, flag, price, made_on) "
          "VALUES ('X1', 'CD', 1.00, '2026/04/02')")
c.execute("SELECT made_on, typeof(made_on), date(made_on) FROM t_types")

# 钱:DECIMAL 在这是浮点
c.execute("CREATE TABLE t_money (id INTEGER PRIMARY KEY, amount DECIMAL(8,2))")
c.execute("INSERT INTO t_money (amount) VALUES (0.1), (0.2)")
c.execute("SELECT CAST(SUM(amount) AS TEXT), SUM(amount) = 0.3 FROM t_money")
c.execute("SELECT SUM(CAST(ROUND(amount * 100) AS INTEGER)) / 100.0 FROM t_money")

实跑输出:

--------------------------------------------------------------------
第七课:数据类型,以及这个引擎为什么不拦你
--------------------------------------------------------------------

  照 MySQL 的写法建一张表:五种类型都用上
  SQL> CREATE TABLE t_types ( id INTEGER PRIMARY KEY, code VARCHAR(10), flag CHAR(4),
       price DECIMAL(8,2), made_on DATE)
  完成
  **这五个类型名 MySQL 有,SQLite 只是「认这个名字」,并不照它执行。**

  塞一个超长的值:MySQL 会拦,这里照收
  SQL> INSERT INTO t_types (code, flag, price, made_on) VALUES ('ABCDEFGHIJKLMNOP', 'AB', 9.90,
       '2026-04-01')
  影响 1 行
  SQL> SELECT code, length(code) AS chars FROM t_types
  code                 chars
  ---------------------------
  ABCDEFGHIJKLMNOP     16
  (1 行)

  **`VARCHAR(10)` 这次没拦住 16 个字符——SQLite 的长度声明只是「亲和性提示」,不做强制。**
  MySQL 在严格模式下会直接报 `Data too long for column`。
  **这条差异很要紧**:在本沙箱能跑过的建表语句,搬到 MySQL 上可能插不进去。
  **所以长度还是要照 MySQL 的规矩定。**

  DECIMAL 在这里退化成浮点:钱算不准的那 0.01
  SQL> SELECT amount, typeof(amount) AS store_type FROM t_money
  amount     store_type
  ----------------------
  0.10       real
  0.20       real
  (2 行)

  SQL> SELECT CAST(SUM(amount) AS TEXT) AS total_raw,
       SUM(amount) = 0.3 AS is_exactly_03 FROM t_money
  total_raw               is_exactly_03
  --------------------------------------
  0.30000000000000004     0
  (1 行)

  **`total_raw` 那一列露出了真面目:0.30000000000000004。**
  **它和 0.3 不相等(`is_exactly_03` 给 0),只是平时按两位小数显示看不出来。**
  **原因是这里的 `DECIMAL(8,2)` 存成了二进制浮点(`typeof` 给 `real`)。**
  **MySQL 的 DECIMAL 是定点数,存 0.1 就是 0.1,不会出这个误差。**
  **所以在 MySQL 里「钱用 DECIMAL」是硬道理;在本沙箱要算准,得先乘 100 转成整数算。**

  用整数分算钱:把误差消掉
  SQL> SELECT SUM(CAST(ROUND(amount * 100) AS INTEGER)) / 100.0 AS total_yuan FROM t_money
  total_yuan
  -----------
  0.30
  (1 行)

  **先乘 100 变整数、加起来、再除回去,结果就是 0.30。**
  **这是所有语言的通用解法,不是 SQL 的技巧。**

  日期在这里就是一个字符串
  SQL> INSERT INTO t_types (code, flag, price, made_on) VALUES ('X1', 'CD', 1.00, '2026/04/02')
  影响 1 行
  SQL> SELECT made_on, typeof(made_on) AS store_type,
       date(made_on) AS parsed FROM t_types WHERE code = 'X1'
  made_on        store_type     parsed
  -----------------------------------------
  2026/04/02     text           NULL
  (1 行)

  **`typeof` 给 `text`——日期是当字符串存的,格式没人管,`2026/04/02` 也照收。**
  **MySQL 的 DATE 只认 `YYYY-MM-DD`,别的格式直接报错或给 0000-00-00。**
  **在本沙箱要算日期差,得靠 `date()` 这类函数自己解析。**

  所以这张对照表要记住
  分类      MySQL 的类型                            本沙箱(SQLite)
  ---------------------------------------------------------------------
  整数      tinyint/int/bigint(分宽度)          一律 INTEGER
  小数      decimal 定点,float/double 浮点        REAL(浮点)
  字符串    char 定长、varchar 变长、text 长文本    TEXT,长度不强制
  日期      date/time/datetime/timestamp         TEXT,格式自己保证
  二进制    binary/varbinary/blob                 BLOB
  (5 行)
  **一句话:MySQL 的类型是「关卡」,SQLite 的类型是「标签」。**
  **所以本课的建表语句照 MySQL 写,跑起来的数据由 SQLite 管,差异逐处标出。**

第一段建表。 VARCHAR(10)、CHAR(4)、DECIMAL(8,2)、DATE 都写上了, MySQL 照这样建没问题,这里只是「认名字」。 第二段超长。 16 个字符进了 VARCHAR(10), length() 量出来 16——MySQL 严格模式这里会直接报错。 第三段小数。 typeof 给 real,说明 DECIMAL 退化成了浮点; 0.1 加 0.2 得 0.30000000000000004,和 0.3 不相等。 第四段转整数算。 乘 100、求和、再除回去,得 0.30——这是所有语言处理钱的通用办法。 第五段日期。 2026/04/02 照收,typeof 给 text,date() 解析不了就给 NULL。 第六段对照表。 五类逐一对照,一句话收尾:MySQL 的类型是关卡,SQLite 的类型是标签。

30 秒实验

  1. 把 VARCHAR(10) 改成 VARCHAR(5),16 个字符还进得去吗?为什么?
  2. (先想再跑)SELECT typeof(9.90), typeof(9) 分别给什么?
  3. 用 CAST(amount * 100 AS INTEGER) 求和,为什么比直接 SUM 准?

常见误区

  1. 一律用 VARCHAR(255)。 占索引空间、掩盖真实长度,还容易让人误以为「够长了」。
  2. 钱用 FLOAT/DOUBLE。 浮点存不准,0.1 加 0.2 都不等于 0.3;钱必须 DECIMAL。
  3. 时间用 TIMESTAMP。 2038 年就到头了,而且随时区换算,跨时区系统里最容易错。
自测三问(点开对答案) 1. DECIMAL 和 FLOAT 的根本区别是什么? —— DECIMAL 是定点数,按十进制位存,0.1 就是 0.1; FLOAT/DOUBLE 是二进制浮点,很多十进制小数存不精确,算钱会出误差。 2. CHAR 和 VARCHAR 差在哪? —— CHAR 定长,不足补空格、取出时删掉;VARCHAR 变长,按实际长度存,另存一个长度字节。 长度固定用 CHAR,其余用 VARCHAR。 3. 为什么本沙箱能往 VARCHAR(10) 里塞 16 个字符? —— 因为 SQLite 的长度声明只是类型亲和性提示,不强制;MySQL 在严格模式下会报 Data too long。

类型定完了, 下一篇是最不起眼但天天要用的东西:注释。 它不只是给人看的,还是调试时最快的一把刀。

相关笔记