1 建表规约
1.1 【强制】是否字段
表达是与否概念的字段,必须使用 is_xxx
的方式命名。
数据类型是 unsigned tinyint
:
说明:任何字段如果为非负数,必须是 unsigned。
# 正例,删除
is_deleted 1表示删除、0表示未删除
1.2【强制】表名、字段 规范
- 表名、字段名必须使用小写字母或数据
- 禁止出现数字开头
- 禁止两个下划线中间只出现数字
数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
MySQL 在 Windows 中不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大小字母,避免节外生枝。
# 正例
aliyun_admin、rdc_config、level3_name
# 反例
AliyunAdmin、rdcConfig、level_3_name
1.3【强制】表名不使用负数名词
说明:表名应该仅仅表示里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。
1.4【强制】禁用保留字
禁用保留字,如 desc、range、match、delayed等,请参考 MySQL 官方保留字。
5.1.5【强制】索引名
主键索引名为: pk_字段名
唯一索引名为:uk_字段名
普通索引名为:idx_字段名
说明:
pk_
即 primary key
uk_
即 unique key
idx_
即 index 的简称
5.1.6【强制】小数类型为 decimal
说明:在存储的时候,float 和 double 都存在精度损失问题,很可能在比较值的时候,得到不正确的结构。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
1.7【强制】字符串相等,使用 char
如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
1.8【强制】varchar、text
varchar 是可变长字符串,不预选分配粗出空间,长度不超过 5000。
如果长度超过5000,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其他字段索引效率。
1.9【强制】表必备字段:id、create_time、update_time
说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增,步长为1。
create_time、update_time 的类型均为 datetime 类型。
1.10【推荐】表命名规则
表命名最好遵循: 业务名称_表的作用
# 正例
alipay_task
force_project
teade_config
1.11【推荐】库名与应用名称尽量一致
库名与应用名称尽量一致
1.12【推荐】字段变动,加注释
如果修改字段含义,或对字段表示的状态追加时,需要及时更新字段注释。
1.13【推荐】字段允许适当冗余
字段允许适当冗余,已提高查询性能,但必须考虑数据一致。冗余字段应遵循:
- 不是频繁修改的字段
- 不是 varchar 超长字段,更不能是 text 字段
- 不是唯一索引的字段
# 正例
商品类目名称使用频率高。字段长度短,名称基本不变,可在相关联的表中冗余存储类目名称,避免关联查询。
1.14【推荐】分表分库
单表行数超过 500 万行,或者单表容量超过 2GB,才推荐进行分库分表。
说明:如果预计 3 年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
1.15【参考】合适的字符存储长度
合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。
对象 |
年龄区间 |
类型 |
字节 |
表示范围 |
人 |
250岁之内 |
tinyint unsigned |
1 |
无符号值:0~255 |
龟 |
数百岁 |
smallint unsigned |
2 |
无符号值:0~65535 |
恐龙化石 |
数千万年 |
int unsigned |
4 |
无符号值:0~约42.9亿 |
太阳 |
约 50 亿年 |
bigint unsigned |
8 |
无符号值:0~约10的19次方 |
2 索引规约
2.1 【强制】唯一索引
业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
**说明:**不要以为唯一索引影响了 insert 的速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
2.2【强制】禁止3个以上表 join
超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;
多表关联查询时,保证被关联的字段需要有索引。
**说明:**即使双表 join 也需要注意表索引、SQL 性能。
2.3【强制】varchar 建立索引,必须指定长度
在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本长度,决定索引长度即可。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型的数据,长度为 20 的索引,区分度会高达 90% 以上,可以使用 count(distinct left(列名, 索引长度))/count(*)
的区分度来确定。
2.4 【强制】页面搜索,请走搜索引擎
页面搜索,严禁使用走模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有 B-Tee 的最左前缀匹配的特性,如果左边的值未确定,那么无法使用此索引。
2.5 【推荐】order by 放最后
如果有 order by 的场景,请注意利用索引的有序性。order by 最火的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
# 正例
where a=? and b=? order by c;
# 反例
索引如果存在范围查询,那么索引的有序性无法利用,如:
where a>10 order by b;
其中,索引 a_b 无法排序。
2.6【推荐】覆盖索引,避免回表
利用覆盖索引来进行查询操作,避免回表。
说明:我们看一本,只需要就看目录即可知道章节标题,无需翻到指定页面查看查看标题。
# 正例
能够建立索引的种类分为:主键索引、唯一索引、普通索引三种,而覆盖索引指示一种查询的一种效果,用explain的结果,extra列就会出现:using index
2.7【推荐】利用延迟关联或者子查询优化超多分页场景
说明:MySQL 并不是跳过 offset 行,而是去 offset + N 行,然后返回放弃前 offsert行,返回 N 行,那当 offset 特别大的时候,效率就非常低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
# 正例
# 先快速定位需要获取的 id 段,然后在关联
select a.* from 表1 a,
(select id from 表1 where 条件 limit 100000, 20) b
where a.id=b.id;
2.8【推荐】SQL 性能优化目标
SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
说明:
- consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
- ref 指的是使用普通的索引(normal index)
- range 对索引进行范围检索。
# 反例
explain 表中的结构,type=index,索引物理文件全扫描,速度非常慢,这个index界别比较range,还低,与全表扫描是大巫见小巫。
2.9【推荐】建立索引的时候,区分度高的放到最左边
# 正例
# 如果 a 列几乎接近于唯一值,那么只需要单键 idx_a 素索引即可。
where a=? and b=?
说明:存在非等号和等号混合是,在建立索引时,请吧等号条件列前置
where c>? and d=?
其中,c的区分度更高,也必须把 d 放在索引的最前列,即索引 idx_d_c。
2.10【推荐】防止因字段类型不同,导致索引失效
防止因字段类型不同造成的隐式转换,导致索引失效。
2.11 【参考】创建索引时避免有如下极端误解
- 宁滥勿缺,认为一个查询就需要建立一个索引。
- 宁缺毋滥,任务索引会消耗空间、严重拖慢记录的更新,以及行的新增速度。
- 抵制唯一索引。认为业务的唯一性,一律需要在应用层通过“先查后插”方式解决。
5.3 SQL 语句
4 ORM 映射
<h2><a id="1__0"></a>1 建表规约</h2>
<h3><a id="11__2"></a>1.1 【强制】是否字段</h3>
<p>表达是与否概念的字段,必须使用 <code>is_xxx</code> 的方式命名。</p>
<p>数据类型是 <code>unsigned tinyint </code>:</p>
<ul>
<li>1 表示 是</li>
<li>0 表示 否</li>
</ul>
<p>说明:任何字段如果为非负数,必须是 unsigned。</p>
<pre><div class="hljs"><code class="lang-shell"><span class="hljs-meta"># </span><span class="language-bash">正例,删除</span>
is_deleted 1表示删除、0表示未删除
</code></div></pre>
<h3><a id="12__18"></a>1.2【强制】表名、字段 规范</h3>
<ul>
<li>表名、字段名必须使用小写字母或数据</li>
<li>禁止出现数字开头</li>
<li>禁止两个下划线中间只出现数字</li>
</ul>
<p>数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。</p>
<p>MySQL 在 Windows 中不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大小字母,避免节外生枝。</p>
<pre><div class="hljs"><code class="lang-shell"><span class="hljs-meta"># </span><span class="language-bash">正例</span>
aliyun_admin、rdc_config、level3_name
<span class="hljs-meta">
# </span><span class="language-bash">反例</span>
AliyunAdmin、rdcConfig、level_3_name
</code></div></pre>
<h3><a id="13_36"></a>1.3【强制】表名不使用负数名词</h3>
<p>说明:表名应该仅仅表示里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。</p>
<h3><a id="14_40"></a>1.4【强制】禁用保留字</h3>
<p>禁用保留字,如 desc、range、match、delayed等,请参考 MySQL 官方保留字。</p>
<h3><a id="515_44"></a>5.1.5【强制】索引名</h3>
<p>主键索引名为: <code>pk_字段名</code></p>
<p>唯一索引名为:<code>uk_字段名</code></p>
<p>普通索引名为:<code>idx_字段名</code></p>
<p>说明:</p>
<ul>
<li><code>pk_</code> 即 primary key</li>
<li><code>uk_</code> 即 unique key</li>
<li><code>idx_</code> 即 index 的简称</li>
</ul>
<h3><a id="516_decimal_58"></a>5.1.6【强制】小数类型为 decimal</h3>
<p>说明:在存储的时候,float 和 double 都存在精度损失问题,很可能在比较值的时候,得到不正确的结构。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。</p>
<h3><a id="17_char_62"></a>1.7【强制】字符串相等,使用 char</h3>
<p>如果存储的字符串长度几乎相等,使用 char 定长字符串类型。</p>
<h3><a id="18varchartext_66"></a>1.8【强制】varchar、text</h3>
<p>varchar 是可变长字符串,不预选分配粗出空间,长度不超过 5000。</p>
<p>如果长度超过5000,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其他字段索引效率。</p>
<h3><a id="19idcreate_timeupdate_time_72"></a>1.9【强制】表必备字段:id、create_time、update_time</h3>
<p>说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增,步长为1。</p>
<p>create_time、update_time 的类型均为 datetime 类型。</p>
<h3><a id="110_78"></a>1.10【推荐】表命名规则</h3>
<p>表命名最好遵循: <code>业务名称_表的作用</code></p>
<pre><div class="hljs"><code class="lang-shell"><span class="hljs-meta"># </span><span class="language-bash">正例</span>
alipay_task
force_project
teade_config
</code></div></pre>
<h3><a id="111_89"></a>1.11【推荐】库名与应用名称尽量一致</h3>
<p>库名与应用名称尽量一致</p>
<h3><a id="112_93"></a>1.12【推荐】字段变动,加注释</h3>
<p>如果修改字段含义,或对字段表示的状态追加时,需要及时更新字段注释。</p>
<h3><a id="113_97"></a>1.13【推荐】字段允许适当冗余</h3>
<p>字段允许适当冗余,已提高查询性能,但必须考虑数据一致。冗余字段应遵循:</p>
<ul>
<li>不是频繁修改的字段</li>
<li>不是 varchar 超长字段,更不能是 text 字段</li>
<li>不是唯一索引的字段</li>
</ul>
<pre><div class="hljs"><code class="lang-shell"><span class="hljs-meta"># </span><span class="language-bash">正例</span>
商品类目名称使用频率高。字段长度短,名称基本不变,可在相关联的表中冗余存储类目名称,避免关联查询。
</code></div></pre>
<h3><a id="114_110"></a>1.14【推荐】分表分库</h3>
<p>单表行数超过 500 万行,或者单表容量超过 2GB,才推荐进行分库分表。</p>
<p>说明:如果预计 3 年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。</p>
<h3><a id="115_116"></a>1.15【参考】合适的字符存储长度</h3>
<p>合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。</p>
<p>正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。</p>
<table>
<thead>
<tr>
<th>对象</th>
<th>年龄区间</th>
<th>类型</th>
<th>字节</th>
<th>表示范围</th>
</tr>
</thead>
<tbody>
<tr>
<td>人</td>
<td>250岁之内</td>
<td>tinyint unsigned</td>
<td>1</td>
<td>无符号值:0~255</td>
</tr>
<tr>
<td>龟</td>
<td>数百岁</td>
<td>smallint unsigned</td>
<td>2</td>
<td>无符号值:0~65535</td>
</tr>
<tr>
<td>恐龙化石</td>
<td>数千万年</td>
<td>int unsigned</td>
<td>4</td>
<td>无符号值:0~约42.9亿</td>
</tr>
<tr>
<td>太阳</td>
<td>约 50 亿年</td>
<td>bigint unsigned</td>
<td>8</td>
<td>无符号值:0~约10的19次方</td>
</tr>
</tbody>
</table>
<h2><a id="2__129"></a>2 索引规约</h2>
<h3><a id="21__131"></a>2.1 【强制】唯一索引</h3>
<p>业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。</p>
<p>**说明:**不要以为唯一索引影响了 insert 的速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。</p>
<h3><a id="223_join_137"></a>2.2【强制】禁止3个以上表 join</h3>
<p>超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;</p>
<p>多表关联查询时,保证被关联的字段需要有索引。</p>
<p>**说明:**即使双表 join 也需要注意表索引、SQL 性能。</p>
<h3><a id="23varchar__145"></a>2.3【强制】varchar 建立索引,必须指定长度</h3>
<p>在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本长度,决定索引长度即可。</p>
<p>说明:索引的长度与区分度是一对矛盾体,一般对字符串类型的数据,长度为 20 的索引,区分度会高达 90% 以上,可以使用 <code>count(distinct left(列名, 索引长度))/count(*)</code> 的区分度来确定。</p>
<h3><a id="24__151"></a>2.4 【强制】页面搜索,请走搜索引擎</h3>
<p>页面搜索,严禁使用走模糊或者全模糊,如果需要请走搜索引擎来解决。</p>
<p>说明:索引文件具有 B-Tee 的最左前缀匹配的特性,如果左边的值未确定,那么无法使用此索引。</p>
<h3><a id="25_order_by__157"></a>2.5 【推荐】order by 放最后</h3>
<p>如果有 order by 的场景,请注意利用索引的<strong>有序性</strong>。order by 最火的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。</p>
<pre><div class="hljs"><code class="lang-shell"><span class="hljs-meta"># </span><span class="language-bash">正例</span>
where a=? and b=? order by c;
<span class="hljs-meta">
# </span><span class="language-bash">反例</span>
索引如果存在范围查询,那么索引的有序性无法利用,如:
where a>10 order by b;
其中,索引 a_b 无法排序。
</code></div></pre>
<h3><a id="26_171"></a>2.6【推荐】覆盖索引,避免回表</h3>
<p>利用覆盖索引来进行查询操作,避免回表。</p>
<p>说明:我们看一本,只需要就看目录即可知道章节标题,无需翻到指定页面查看查看标题。</p>
<pre><div class="hljs"><code class="lang-shell"><span class="hljs-meta"># </span><span class="language-bash">正例</span>
能够建立索引的种类分为:主键索引、唯一索引、普通索引三种,而覆盖索引指示一种查询的一种效果,用explain的结果,extra列就会出现:using index
</code></div></pre>
<h3><a id="27_182"></a>2.7【推荐】利用延迟关联或者子查询优化超多分页场景</h3>
<p>说明:MySQL 并不是跳过 offset 行,而是去 offset + N 行,然后返回放弃前 offsert行,返回 N 行,那当 offset 特别大的时候,效率就非常低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。</p>
<pre><div class="hljs"><code class="lang-sql"># 正例
# 先快速定位需要获取的 id 段,然后在关联
<span class="hljs-keyword">select</span> a.<span class="hljs-operator">*</span> <span class="hljs-keyword">from</span> 表<span class="hljs-number">1</span> a,
(<span class="hljs-keyword">select</span> id <span class="hljs-keyword">from</span> 表<span class="hljs-number">1</span> <span class="hljs-keyword">where</span> 条件 limit <span class="hljs-number">100000</span>, <span class="hljs-number">20</span>) b
<span class="hljs-keyword">where</span> a.id<span class="hljs-operator">=</span>b.id;
</code></div></pre>
<h3><a id="28SQL__194"></a>2.8【推荐】SQL 性能优化目标</h3>
<p>SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。</p>
<p>说明:</p>
<ul>
<li>consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。</li>
<li>ref 指的是使用普通的索引(normal index)</li>
<li>range 对索引进行范围检索。</li>
</ul>
<pre><div class="hljs"><code class="lang-shell"><span class="hljs-meta"># </span><span class="language-bash">反例</span>
explain 表中的结构,type=index,索引物理文件全扫描,速度非常慢,这个index界别比较range,还低,与全表扫描是大巫见小巫。
</code></div></pre>
<h3><a id="29_209"></a>2.9【推荐】建立索引的时候,区分度高的放到最左边</h3>
<pre><div class="hljs"><code class="lang-shell"><span class="hljs-meta"># </span><span class="language-bash">正例</span>
<span class="hljs-meta"># </span><span class="language-bash">如果 a 列几乎接近于唯一值,那么只需要单键 idx_a 素索引即可。</span>
where a=? and b=?
</code></div></pre>
<p>说明:存在非等号和等号混合是,在建立索引时,请吧等号条件列前置</p>
<pre><div class="hljs"><code class="lang-shell">where c>? and d=?
其中,c的区分度更高,也必须把 d 放在索引的最前列,即索引 idx_d_c。
</code></div></pre>
<h3><a id="210_224"></a>2.10【推荐】防止因字段类型不同,导致索引失效</h3>
<p>防止因字段类型不同造成的隐式转换,导致索引失效。</p>
<h3><a id="211__228"></a>2.11 【参考】创建索引时避免有如下极端误解</h3>
<ul>
<li>宁滥勿缺,认为一个查询就需要建立一个索引。</li>
<li>宁缺毋滥,任务索引会消耗空间、严重拖慢记录的更新,以及行的新增速度。</li>
<li>抵制唯一索引。认为业务的唯一性,一律需要在应用层通过“先查后插”方式解决。</li>
</ul>
<h2><a id="53_SQL__234"></a>5.3 SQL 语句</h2>
<h2><a id="4_ORM__238"></a>4 ORM 映射</h2>
留言