mysql命令集合及学习总结

110 阅读24分钟

掘金小册:MySQL 是怎样运行的:从根儿上理解 MySQL

mysql 服务端是一个程序,启动后就是一个进程,每当有一个客户端进来,就启用一个线程进行连接,当客户端断开连接后,并不立即结束线程,而是保留着以备下次客户端连接。

mysql 是C语言实现的。

建表

文档链接 CREATE TABLE 表名 (列的信息) [[DEFAULT] CHARACTER SET 字符集名称] [COLLATE 比较规则名称]]

其中的DEFAULT可以省略,并不影响语句的语义。

使用 RENAME 可以把表重命名,也可以把一张表从一个库转移到另外一个库,而不是说非得通过 export import 的方式。不过在转移之后,原来的库中就没有这张表了。

主键

InnoDB表对主键的生成策略:优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个Unique键作为主键,如果表中连Unique键都没有定义的话,则InnoDB会为表默认添加一个名为row_id的隐藏列作为主键。所以我们从上表中可以看出:InnoDB存储引擎会为每条记录都添加 transaction_id 和 roll_pointer 这两个列,但是 row_id 是可选的(在没有自定义主键以及Unique键的情况下才会添加该列)。这些隐藏列的值不用我们操心,InnoDB存储引擎会自己帮我们生成的。

外键、约束

文档链接 外键是一种约束。是在定义表的时候放在列的后面定义的。

CONSTRAINT [外键名称] FOREIGN KEY(列1, 列2, ...) REFERENCES 父表名(父列1, 父列2, ...);

CREATE TABLE student_score (
    number INT,
    subject VARCHAR(30),
    score TINYINT,
    PRIMARY KEY (number, subject),
    CONSTRAINT FOREIGN KEY(number) REFERENCES student_info(number)
);

如果A表中的某个列或者某些列依赖与B表中的某个列或者某些列,那么就称A表为子表,B表为父表。B表中的那些列应该是有索引的。

主键、唯一索引、外键等都属于一种约束,只不过前两者都会建立索引(而且不用写关键词CONSTRAINT),而外键是纯约束。 约束和索引是并列的。索引可以是约束(如主键约束、唯一索引约束),也可以不是约束。约束也有不是索引的(如外键约束)。

数值显示宽度

文档链接 其他链接 只有mysql的数值类型才有显示宽度,比如int(10),他不影响类型的真实存储范围,只有在 zerofill 的情况下才会在数值前面补0。

索引

mysql 为了加快查询的速度,加入了索引。 索引是独立于源数据之外的数据。 索引并不是都加上好,因为在更新源数据的同时还需要修改索引的数据。 mysql 的 InnoDB 引擎使用的索引机制是 B Tree。

WHERE子句中的几个搜索条件的顺序对查询结果有啥影响么?
答案是:没影响哈。MySQL有一个叫查询优化器的东东,会分析这些搜索条件并且按照可以使用的索引中列的顺序来决定先使用哪个搜索条件,后使用哪个搜索条件。
SELECT * FROM person_info WHERE birthday = '1990-09-27' AND phone_number = '15123983239' AND name = 'Ashburn';

槽、页的目录项

同一个页中,记录分为几个组,每个组的最后一个记录的地址偏移量就是槽。同一个也的Page Directory(页目录)有至少2个槽。链接

页分裂的过程中,我们给这些页做个目录,每个页对应一个目录项,每个目录项包括下边两个部分: 页的用户记录中最小的主键值,我们用key来表示。 页号,我们用page_no表示。

聚簇索引

聚簇索引就是主键的索引,叶子节点有全部的数据。 二级索引因为叶子节点只有索引列和主键列,需要用聚簇索引再进行一次回表,所以称为二级索引。 联合索引就是二级索引,除非是联合主键,那就是聚簇索引。 聚簇索引不是人为创建的,默认就有。

InnoDB和MyISAM会自动为主键或者声明为UNIQUE的列去自动建立B+树索引,但是如果我们想为其他的列建立索引就需要我们显式的去指明 ,使用 key 或 index。 Unique的应该是二级索引了

独立表空间结构

文档链接 最终效果图: 最终效果图

初心:仅仅是想提高向表插入数据的效率又不至于数据量少的表浪费空间。 向表中插入数据本质:就是向表中各个索引的叶子节点段、非叶子节点段插入数据。

# 页page   16k

# 区extent 64个连续的页(1M)
引入了区(extent)的概念的原因:在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区为单位分配,甚至在表中的数据十分非常特别多的时候,可以一次性分配多个连续的区。虽然可能造成一点点空间的浪费(数据不足填充满整个区),但是从性能角度看,可以消除很多的随机I/O,功大于过嘛!

# 组 256个区(256M)

# 段 
概念:某些零散的页面以及一些完整的区的集合。
存放叶子节点的区的集合就算是一个段(segment),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。
加入了碎片区的概念后,就组合成了上面的概念。

# 碎片区
也是区,避免了 每次添加一个索引都要多申请2M的存储空间。
在刚开始向表中插入数据的时候(插入数据,所有索引都自然会跟着变化),段是从某个碎片区以单个页面为单位来分配存储空间的。
当某个段已经占用了32个碎片区页面(区的一半)之后,就会以完整的区为单位来分配存储空间。

第一个组最开始的3个页面的类型是固定的,也就是说extent 0这个区最开始的3个页面的类型是固定的,分别是:FSP_HDR IBUF_BITMAP INODE

其余各组最开始的2个页面的类型是固定的,分别是:XDES IBUF_BITMAP
用户是不能直接访问InnoDB的内部系统表的,除非你直接去解析系统表空间对应文件系统上的文件。不过设计InnoDB的大叔考虑到查看这些表的内容可能有助于大家分析问题,所以在系统数据库information_schema中提供了一些以innodb_sys开头的表:
mysql> USE information_schema;
Database changed

mysql> SHOW TABLES LIKE 'innodb_sys%';
+--------------------------------------------+
| Tables_in_information_schema (innodb_sys%) |
+--------------------------------------------+
| INNODB_SYS_DATAFILES                       |
| INNODB_SYS_VIRTUAL                         | 
  ...
+--------------------------------------------+
10 rows in set (0.00 sec)

这些表并不是真正的内部系统表(内部系统表就是我们上边唠叨的以SYS开头的那些表),而是在存储引擎启动时读取这些以SYS开头的系统表,然后填充到这些以INNODB_SYS开头的表中,但供大家参考已经足矣。

链表

链表就是一个数据结构概念。

  • 寄生的链表 数据页的page header里面有记录前一个数据页和下一个数据页的地址信息,这就组成了一个双向链表,双向链表相邻的两个页之间的物理位置可能离得非常远。等于说链表结构内嵌在数据的信息内。这样的链表既放在内存中,也跟数据一起存在于磁盘上。
  • 专门的链表 比如undo日志、lsn等文档里面的那些链表,是专门做的链表结构,这样的链表应该是放在内存中,另外单独自己作为链表的数据存在于磁盘上?

在写入undo日志的过程中会使用到多个链表,很多链表都有同样的节点结构。

查询

查询中给列名起别名可以把 AS 省略掉,不过尽量还是不要省略了,以免时间长了忘记,中间加个逗号就会bug掉。

分组查询

文档链接

# 顺序
SELECT [DISTINCT] 查询列表
[FROM 表名]
[WHERE 布尔表达式]
[GROUP BY 分组列表 ]
[HAVING 分组过滤条件]
[ORDER BY 排序列表]
[LIMIT 开始行, 限制条数]

被where子句过滤掉的根本不会进行分组,分组后,符合having子句条件的,才会出现在最终的结果集中。

组合查询

文档链接 组合查询等于说把两个查询的结果拼到一起。如果不用union all, 就会对两个结果集中重复的结果进行去重,另外会用第一个结果集的列名作为最终结果集的列名(后面查询的结果如果类型不同的话还会进行隐式类型转换)。

查询缓存

从MySQL 5.7.20开始,不推荐使用查询缓存,并在MySQL 8.0中删除。 小册第2节。

查询优化

关于执行计划 EXPLAIN:

  • 每个 select 可以有多张表,
  • 每张表都对应执行计划的一行记录,都对应一个id,
  • 如果多个表属于同个 select, 他们的记录的 id 相同
  • 多个 select 可能转化为同一个 select,如子查询转化为连接
  • 每个 select 对应一个 select_type

物化表

文档

# 物化是真的有操作,产生了一个物理上的临时表
设计MySQL的大叔把这个将子查询结果集中的记录保存到临时表的过程称之为物化(英文名:Materialize)。为了方便起见,我们就把那个存储子查询结果集的临时表称之为物化表。正因为物化表中的记录都建立了索引(基于内存的物化表有哈希索引,基于磁盘的有B+树索引),通过索引执行IN语句判断某个操作数在不在子查询结果集中变得非常快,从而提升了子查询语句的性能。

派生表

# 派生表只是一个概念,有可能会被物化掉成为物化表
我们前边说过把子查询放在外层查询的FROM子句后,那么这个子查询的结果相当于一个派生表,比如下边这个查询:

SELECT * FROM  (
        SELECT id AS d_id,  key3 AS d_key3 FROM s2 WHERE key1 = 'a'
    ) AS derived_s1 WHERE d_key3 = 'a';
子查询( SELECT id AS d_id, key3 AS d_key3 FROM s2 WHERE key1 = 'a')的结果就相当于一个派生表,这个表的名称是derived_s1,该表有两个列,分别是d_id和d_key3。

所以MySQL在执行带有派生表的时候,优先尝试把派生表和外层查询合并掉,如果不行的话,再把派生表物化掉执行查询。

传参格式

小册第3节 mysqld --default-storage-engine=MyISAM 启动选项的通用格式 错误 mysqld --default-storage-engine = MyISAM 不能有空格

使用短形式指定启动选项时,选项名和选项值之间可以没有间隙,或者用空白字符隔开(-p选项有些特殊,-p和密码值之间不能有空白字符),也就是说下边的是等价的: mysqld -P3307 在启动服务器程序时指定监听的端口号 mysqld -P 3307

字符集

CHARACTER SET|CHARSET 字符集 SHOW (CHARACTER SET|CHARSET) [LIKE 匹配的模式]; 查看字符集 SHOW COLLATION LIKE 'utf8\_%'; 查看 utf8 字符集下的比较规则 SHOW VARIABLES LIKE 'character_set_server'; 查看服务器级别的字符集 SHOW VARIABLES LIKE 'collation_server'; 查看服务器级别的比较规则

连接

内连接和外连接的根本区别就是在驱动表中的记录不符合ON子句中的连接条件时不会把该记录加入到最后的结果集

# 内连接,
# 下面4句是等价的
SELECT * FROM t1, t2;
SELECT * FROM t1 JOIN t2;
SELECT * FROM t1 INNER JOIN t2;
SELECT * FROM t1 CROSS JOIN t2;

# 外连接
对于左(外)连接和右(外)连接来说,必须使用ON子句来指出连接条件
SELECT * FROM t1 LEFT [OUTER] JOIN t2 ON 连接条件 [WHERE 普通过滤条件];

where on

# WHERE
WHERE子句中的过滤条件就是我们平时见的那种,不论是内连接还是外连接,凡是不符合WHERE子句中的过滤条件的记录都不会被加入最后的结果集。

# ON
ON子句是专门为外连接驱动表中的记录在被驱动表找不到匹配记录时应不应该把该记录加入结果集这个场景下提出的。**在内连接中ON子句和WHERE子句是等价的**,所以内连接中不要求强制写明ON子句,二者可以同时存在,共同作为查询条件。
如果在被驱动表中找到的记录不匹配ON子句中的过滤条件,
  对于外连接:那么该记录仍然会被加入到结果集中,对应的被驱动表记录的各个字段使用NULL值填充。
  对于内连接:该记录不会被加入最后的结果集(和where效果一样)。

一般情况下,我们都把只涉及单表的过滤条件放到WHERE子句中,把涉及两表的过滤条件都放到ON子句中,我们也一般把放到ON子句中的过滤条件也称之为连接条件。

子查询

子查询的结果其实就相当于一个集合,集合里的值排不排序一点儿都不重要

相关子查询

SELECT * FROM s1 
    WHERE key1 IN (SELECT common_field FROM s2 WHERE s1.key2 = s2.key2);

将子查询转换为semi-join

看小册的时候,有句话比较重要: 对于s1表的某条记录来说

buffer pool

一句话总结:InnoDBbuffer pool 就是一个缓存的池子,里面由控制块和缓存页组成,二者一一对应。控制块里面有使用这个缓存页的页所属的表空间编号、页号的信息 ,以及这个缓存页本身在 buffer pool 中的地址、链表节点信息、一些锁信息以及LSN信息等信息。 另外空闲、占用的缓存都有各种各样的链表把他们的信息组织起来。

LRU 链表分为2半,young old 核心:尽量高效的提高 Buffer Pool 的缓存命中率 show variables like "innodb_buffer_pool%"; 查看 buffer pool 各参数 SHOW ENGINE INNODB STATUS; 查看关于InnoDB存储引擎运行过程中的一些状态信息,其中就包括Buffer Pool的一些信息

两种日志

page header 里面那些 lsn 什么的主要是为了在 redo 的时候判断 是不是比checkpoint_lsn 更加新,这样就在恢复的时候不用再刷新到磁盘了,主要是为了提升恢复数据的效率,另外 page 里面的那些信息,其实主要为了 redo undo这些操作,另外像控制块之类的概念都是为了这个。而 redo日志 undo日志、还有这些复杂的字节、标志位什么的其实都是为了保持数据库和现实世界的一致性。

可以简单记为redo为了防断电,undo日志为了事务回滚。 redo日志写到日志组,undo日志写到FIL_PAGE_UNDO_LOG类型的页面。

redo 日志

假如服务崩溃、断电,怎么保证做过的操作在重启后依然存在。

mtr

Mini Transaction 设计MySQL的大叔把对底层页面中的一次原子访问的过程称之为一个Mini-Transaction,简称mtr,比如上边所说的修改一次Max Row ID的值算是一个Mini-Transaction,向某个索引对应的B+树中插入一条记录的过程也算是一个Mini-Transaction。

包含关系

一个事务可以包含多条语句,每条语句就是一个原子性的事务,mini-transaction,即mtr,而每个mtr又一般会包含多条 redo 日志。

lsn

Log Sequeue Number 文档 当有新的redo日志写入到log buffer时,首先lsn的值会增长,但flushed_to_disk_lsn不变,随后随着不断有log buffer中的日志被刷新到磁盘上,flushed_to_disk_lsn的值也跟着增长。如果两者的值相同时,说明log buffer中的所有redo日志都已经刷新到磁盘中了。

flushed_to_disk_lsn和buf_next_to_write应该是始终保持一致的,即意义相同且值相同,请问为啥要单独设两个变量?

怎么恢复

redo日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统奔溃重启后可以把事务所做的任何修改都恢复出来。

flush链表中的脏页按照修改发生的时间顺序进行排序,也就是按照oldest_modification代表的LSN值进行排序,被多次更新的页面不会重复插入到flush链表中,但是会更新newest_modification属性的值。

存到哪

文档链接 我们前边说mtr运行过程中产生的一组redo日志在mtr结束时会被复制到log buffer中,可是这些日志总在内存里呆着也不是个办法,在一些情况下它们会被刷新到磁盘里。

MySQL的数据目录(使用SHOW VARIABLES LIKE 'datadir'查看)下默认有两个名为ib_logfile0和ib_logfile1的文件,log buffer中的日志默认情况下就是刷新到这两个磁盘文件中。

磁盘上的redo日志文件不只一个,而是以一个日志文件组的形式出现的。这些文件以ib_logfile[数字](数字可以是0、1、2...)的形式进行命名。在将redo日志写入日志文件组时,是从ib_logfile0开始写,如果ib_logfile0写满了,就接着ib_logfile1写,同理,ib_logfile1写满了就去写ib_logfile2,依此类推。如果写到最后一个文件该咋办?那就重新转到ib_logfile0继续写。

undo日志

文档 用于在事务执行中主动或被动回滚,撤销事务的修改。 提交之后我们就不用回滚了,所以undo日志在事务提交之前是有用的。

数据页每一条记录的 roll_pointer 本质就是一个指针,指向记录对应的undo日志。对同一个数据的一系列操作的undo日志还能组成一个版本链。

Undo页面链表按照存储的undo日志所属的大类可以被分为insert undo链表和update undo链表两种。

存到哪

文档链接 表空间其实是由许许多多的页面构成的,页面默认大小为16KB。这些页面有不同的类型,比如类型为FIL_PAGE_INDEX的页面用于存储聚簇索引以及二级索引,类型为FIL_PAGE_TYPE_FSP_HDR的页面用于存储表空间头部信息的,还有其他各种类型的页面,其中有一种称之为FIL_PAGE_UNDO_LOG类型的页面是专门用来存储undo日志的。

为了尽可能提高undo日志的写入效率,不同事务执行过程中产生的undo日志需要被写入到不同的Undo页面链表中。

每一个Undo页面链表都对应着一个段,称之为Undo Log Segment。也就是说链表中的页面都是从这个段里边申请的。

一条undo日志链表也叫一个组。

update操作:

  • 不更新主键的情况:就地更新或先删除掉旧记录,再插入新记录
  • 更新主键的情况,对旧记录只做一个delete mark操作,在事务提交后才由专门的线程做purge操作,把它加入到垃圾链表中。之所以只对旧记录做delete mark操作,是因为别的事务同时也可能访问这条记录,如果把它真正的删除加入到垃圾链表后,别的事务就访问不到了。那为什么不更新主键的情况能先删除旧记录?起码能访问到,删除再插入的时间忽略不计。
  • 有没有可能别的事务访问的这个事务修改了的,但是会回滚的临时变化的数据?

回滚段

Rollback Segment Header是一个16k的页面,这个页面中存放了各个Undo页面链表的frist undo page的页号,他们把这些页号称之为undo slot。

一个事务在执行过程中最多可以分配4个Undo页面链表,在同一时刻不同事务拥有的Undo页面链表是不一样的,所以在同一时刻系统里其实可以有许许多多个Undo页面链表存在。

# Rollback Segment 回滚段【里面其实只有一个页面】
设计InnoDB的大叔规定,每一个Rollback Segment Header页面都对应着一个段,这个段就称为Rollback Segment,翻译过来就是回滚段。与我们之前介绍的各种段不同的是,这个Rollback Segment里其实只有一个页面(这可能是设计InnoDB的大叔们的一种洁癖,他们可能觉得为了某个目的去分配页面的话都得先申请一个段,或者他们觉得虽然目前版本的MySQL里Rollback Segment里其实只有一个页面,但可能之后的版本里会增加页面也说不定)。

在系统表空间的第5号页面中存储了128Rollback Segment Header页面地址,每个Rollback Segment Header就相当于一个回滚段。在Rollback Segment Header页面中,又包含1024个undo slot,每个undo slot都对应一个Undo页面链表。效果如下图

效果

MVCC

# 脏写
一个事务修改了另一个未提交事务修改过的数据

# 脏读
一个事务读到了另一个未提交事务修改过的数据

# 不可重复读
一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值

# 幻读
一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录

# 严重程度
脏写 > 脏读 > 不可重复读 > 幻读

概念

所谓的MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是:生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView就好了。

ReadView

顺着undo日志的版本链去判断与当前事务id的先后顺序,决定可以使用哪个版本的数据。所以版本链是很有用的。

REPEATABLE READ隔离级别下,一个事务在执行过程中只有第一次执行SELECT操作才会生成一个ReadView,之后的SELECT操作都复用这个ReadView,这样也就避免了不可重复读和幻读的问题。

文档 并发事务访问相同记录。

MySQL的默认隔离级别为REPEATABLE READ.

# 一致性读(Consistent Reads)
事务利用MVCC进行的读取操作称之为一致性读,或者一致性无锁读,有的地方也称之为快照读。所有普通的SELECT语句(plain SELECT)在READ COMMITTED、REPEATABLE READ隔离级别下都算是一致性读

# 锁定读(Locking Reads)
共享锁和独占锁。共享锁不同的事务可以同时获取。要想获取独占锁,需要之前的事务释放共享锁或独占锁。要想获取共享锁,需要之前的事务没有独占锁。

# 意向锁
意向共享锁,英文名:Intention Shared Lock,简称IS锁。当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。
意向独占锁,英文名:Intention Exclusive Lock,简称IX锁。当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。
学生在教学楼门口加IS锁时,是不关心教学楼门口是否有IX锁的,维修工在教学楼门口加IX锁时,是不关心教学楼门口是否有IS锁或者其他IX锁的。IS和IX锁只是为了判断当前时间教学楼里有没有被占用的教室用的,也就是在对教学楼加S锁或者X锁时才会用到。


命令

变量分系统变量和状态变量(小册第3节)

SHOW ENGINES; 查看当前服务器程序支持的存储引擎 SHOW CREATE TABLE tablename; 查看(实时的,而不是最初创建时的)表的结构(含引擎) DESCRIBE tablename; 查看表的字段特征 ALTER TABLE tablename ENGINE = InnoDB; 变更表引擎 SHOW VARIABLES LIKE 'default%'; 查看系统变量(系统变量都是用下划线连起来的,不管设置的时候用的是中划线还是下划线,默认显示 SESSION 的系统变量)

# 设置全局系统变量
# 如果某个客户端改变了某个系统变量在`GLOBAL`作用范围的值,并不会影响该系统变量在当前已经连接的客户端作用范围为`SESSION`的值,只会影响后续连入的客户端在作用范围为`SESSION`的值。

SET GLOBAL default_storage_engine = MyISAM;
SET @@GLOBAL.default_storage_engine = MyISAM;
# 设置SESSION(当前会话)系统变量
SET SESSION default_storage_engine = MyISAM;
SET @@SESSION.default_storage_engine = MyISAM;
SET default_storage_engine = MyISAM;
# 在命令行上指定有值的启动选项时需要注意,选项名、=、选项值之间不可以有空白字符,比如写成下边这样就是不正确的(第3节):
mysqld --default-storage-engine = MyISAM

SHOW VARIABLES LIKE 'datadir'; 查看MySQL把数据都存到哪个路径下 SHOW TABLE STATUS LIKE 'single_table'; 查看single_table这个表的统计信息 SHOW VARIABLES LIKE '%dive%'; 查看 index dive 的上限 SHOW INDEX FROM single_table; 查看一下single_table的各个索引的统计数据 SHOW TABLES FROM mysql LIKE '%cost%'; 查询成本常数存在哪个表 SHOW TABLES FROM mysql LIKE 'innodb%'; 查看某个表以及该表索引的统计数据存放到磁盘的哪个表里 SHOW VARIABLES LIKE 'optimizer_trace'; 查看 optimizer trace 功能是否开启 SET optimizer_trace="enabled=on"; 开启 optimizer trace 功能 SELECT * FROM information_schema.OPTIMIZER_TRACE; 查看对上一条查询语句,优化器的具体工作过程 SHOW VARIABLES LIKE 'innodb_old_blocks_pct'; 查看old区域在LRU链表中所占的比例 SHOW ENGINE INNODB STATUS; 查看关于InnoDB存储引擎运行过程中的一些状态信息,其中就包括Buffer Pool的一些信息 SHOW VARIABLES LIKE 'transaction_isolation'; 查看当前会话默认的隔离级别

case when

原生批量修改数据 update-db-avatar/lib/HandleAvatar.js

/**
   * 将需要更新为七牛新地址的用户的头像更新数据库
   *
   * @returns {promise}
   */
  updateDb () {
    let caseStr = ''
    let idList = []
    this.qiniuUrlList.forEach((item, index) => {
      // 只更新符合条件的且关注的,因为未关注的人,这一项为 undefined
      if (item) {
        let id = this.dbUserList[index].id
        caseStr += `when id=${id} then '${item}' `
        idList.push(id)
      }
    })
    let str = `UPDATE auth_userprofile SET avatar = (case ${caseStr} end) where id in (${idList})`

    // 有没有人可以更新,就不再执行sql语句
    return caseStr && this.sequelize.query(str)
  }

字符串

char类型定长,短了可以插入,取出也是短的。但是占用的空间是定的。超过最大长度直接插入失败。 varchar 类型不定长,有最大长度,短了可以插入,取出也是短的,但是占用的空间是不定的,但是占用额外的字符表示字符长度。超过最大长度直接插入失败。

一行记录的大小

小册第5节

MySQL对一条记录占用的最大存储空间是有限制的,除了BLOB或者TEXT类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字节(除以1024就是64k,即4个页)。