对于 Web 线上业务使用 InnoDB,这也是现在 MySQL 默认的数据引擎。
虽然 MyISAM 非常适用于读远远大于写的场景,但是不要在同一个库中混用这两种引擎,因为大数据量情况下的备份和容灾还没有特别靠谱的方案。
字符集
字符集使用 utf8mb4
,MySQL 的 utf8 是假的 utf8,不能保存特殊字符(如手机的emoji表情)
我们更应当在编译或配置文件my.cnf中强制指定服务器级别的字符集,这样应用层不再需要 SET NAMES:
1 | [client] |
数据类型
数值型
尽量选择小的数据类型,小的数据长度,但要留有一定的冗余。
TINYINT (1字节)
SMALLINT(2字节)
MEDIUMINT(3字节)
INT (4字节)
BIGINT(8字节)
如果需要存储小数,使用 decimal,不要使用 float 和 double(存在精度丢失问题)。如果是货币计算,推荐使用整型来进行存储,前端处理小数点。
字符串
varchar 最大的长度是255,声明更大的长度会自动转换为 Text,在 InnoDB 引擎上,应当使用 varchar 代替 char
日期时间
应当优先使用 DATE(3字节)、DATETIME(8字节),他们比字符串占用空间更小
TIMESTAMP(3字节) 特别适合存储记录类的时间(1970-01-01 ~ 2038-01-19),如创建时间、更新时间,并且可以设置默认值与更新默认值。并且这个类型存储为 UTC 时间,存入和读取的时候会自动进行时区转换。
DATETIME 适合存储任意范围的时间(1000-01-01 ~ 9999-12-31),可以使用 NOW() 自动插入当前时间,比较适合存储超出范围、以及相对不变化的时间
还可以使用 INT(10) 来直接存储时间戳,时间范围限制同 TIMESTAMP,主要是查询速度快,适合大量范围查询(网上这么说),缺点是不直观,需要使用 from_unixtime() 转换才能看到结果。
表名与字段名
不要使用保留字:如 desc、match 等
表名、字段名尽量能够表达意思,字段最好增加注释。
表名:一律小写,多个单词用下划线分隔,单词用单数不用复数,如:member, member_detail
字段名:一律小写,多个单词用下划线分隔,字段名不加表名,如:id, name, start_time
关联表:关联表的主键id,可以不用下划线,如 memberid (有争议,我觉得member_id有个下划线好难看)
常用字段命名(供参考,约定后保持统一):
添加时间:created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
,
添加人:createby
更新时间:updated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
,
更新人:updateby
删除时间:deleted timestamp
删除人:deleteby
删除标志:delete tinyint
状态标志:status
如果状态值较多、较复杂,建议使用 enum 类型
引擎对比
主要是 MyISAM 与 InnoDB 的对比
缓存
MyISAM 仅仅缓存索引,不会缓存实际数据信息,他会将这一工作交给操作系统级别的文件系统缓存。所以 MyISAM 缓存优化工作集中在索引缓存优化上。
清空和禁用缓存需要两步:
1 | echo 3 > /proc/sys/vm/drop_caches |
InnoDB 有自己的缓存,不仅仅缓存索引,还缓存表数据,只要你内存足够大。比如在做数据迁移的时候,有时候可以采用倒读的方法。
内存占用
缓存机制基本上决定了内存占用情况。
InnoDB 内存占用较大,并且由 innodb_buffer_pool_size
参数控制,对于独立的 InnoDB 数据库服务器一般要配置为物理内存大小的 70% 左右,原因就是它的缓存机制。
磁盘占用
MyISAM 存储索引与数据是独立存储的。
InnoDB索引与数据整体存储在.ibd文件,一定要开启 innodb_file_per_table 选项,否则 InnoDB 会将所有表存在共享空间中。
整体上 MyISAM 磁盘占用约比 InnoDB 小 20% 左右。
安全性
InnoDB 安全系数很高,innodb_flush_log_at_trx_commit
参数会控制刷写机制,表基本上不会坏,目前我只遇到过因为文件系统崩溃导致数据无法恢复的情况。
MyISAM 在频繁写和更新的时候,经常容易坏,虽然很容易修复(有时也无法修复),但在坏表期间会影响线上业务。
锁定
MyISAM 是表级锁定,写入的时候阻塞读取,还会在读取的时候阻塞写入,尤其是你使用 mysqldump 的时候
InnoDB 是行级锁定,但也不绝对,比如当更新的 SQL 无法确定表的行范围时也会进行表锁
事务支持、外键
InnoDB 支持事务,对稍微大一点规模的Web业务,基本都会需要这个功能,会员注册你要写 member, member_detail 两张表,用户下订单,每个订单可能是一条记录,这样的插入、更新操作都要保证原子性。
注意:需要非常谨慎的使用外键、触发器
并发
MyISAM 的表级锁定决定了在写场景多的情况下多用户并发很差,而 InnoDB 的以下三个参数还可以更好控制并发:
1 | innodb_thread_concurrency # 独立数据库一般为CPU核数 |
写入与更新
并发性能好,并不能说明写入效率高,在千万级别的单条插入我没感觉到两者差异,但 MyISAM 的批量插入性能非常高。
另外 MyISAM 还可以调整 bulk_insert_buffer_size
参数来提高批量插入的速度。
LOAD DATA 也非常快,底层应该也是采用的批量插入。
很明显索引过多会影响插入性能,尤其是当有唯一索引时。
其他
MyISAM 不带 Where 条件的 COUNT(*) 非常快,而 InnoDB 需要全表扫描。
SQL MODE
5.6.6 开始 MySQL 的默认sql_mode为 NO_ENGINE_SUBSTITUTION
,之前的版本为空。
5.7.5 开始增加了 ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES
5.7.7 开始增加了 NO_AUTO_CREATE_USER
5.7.8 开始增加了 ERROR_FOR_DIVISION_BY_ZERO, NO_ZERO_DATE, NO_ZERO_IN_DATE
所以,5.7 最新默认 sql_mode 为 ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE, ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER, NO_ENGINE_SUBSTITUTION
参数说明
NO_ENGINE_SUBSTITUTION 禁用时,当表引擎不存在的时候会自动转换,否则会报错
ERROR_FOR_DIVISION_BY_ZERO 数据库对于除以 0 情况的处理,比如 MOD(N, 0)
NO_AUTO_CREATE_USER 指定 GRANT 语句必须完善认证信息,比如设置密码
NO_ZERO_DATE 指定是否 0000-00-00 是合法的日期格式
NO_ZERO_IN_DATE 更严格的日期格式限制,比如 2010-00-01 或 2010-01-00
ONLY_FULL_GROUP_BY 官方解释没看懂