Skip to main content

MySQL 优化建议


SQL语句及索引优化建议​

MySQL数据库优化手段优先级​

  • SQL语句及索引优化 -> 表结构设计 -> 系统配置 -> 硬件升级
  • 优化成本:硬件升级>系统配置>表结构设计>SQL语句及索引。
  • 优化效果:硬件升级<系统配置<表结构设计<SQL语句及索引。

数据库哪些情况需要建索引​

  • 主键自动建立唯一索引
  • 频繁作为查询的条件的字段应该创建索引
  • 查询中与其他表关联的字段,外键关系建立索引
  • 频繁更新的字段不适合创建索引:因为每次更新不单单是更新了记录还会更新索引,加重IO负担
  • Where条件里用不到的字段不创建索引
  • 单间/组合索引的选择问题(在高并发下倾向创建组合索引)
  • 查询中排序的字段,若通过索引去访问将大大提高排序的速度
  • 查询中统计或者分组字段

MySQL优化核心关注点有哪些?​

  • 开启慢查询日志,定位运行慢的SQL语句
  • 利用explain执行计划,查看SQL执行情况
  • 关注索引使用情况:type
  • 关注Rows:行扫描
  • 关注Extra:没有信息最好
  • 加索引后,查看索引使用情况,index只是覆盖索引,并不算很好的使用索引
  • 如果有关联尽量将索引用到eq_ref或ref级别
  • 复杂SQL可以做成视图,视图在MySQL内部有优化,而且开发也比较友好
  • 对于复杂的SQL要逐一分析,找到比较费时的SQL语句片段进行优化

更多细节请查看后续 《执行计划分析》章节的内容

MySQL系统配置优化的主要目的是什么?​

  1. 保证从内存中读取数据

    MySQL会在内存中保存一定的数据,通过LRU算法将不常访问的数据保存在硬盘文件中。尽可能的扩大内存中的数据量,将数据保存在内存中,从内存中读取数据,可以提升MySQL性能。扩大innodb_buffer_pool_size,能够全然从内存中读取数据。最大限度降低磁盘操作。

  2. 进行数据预热

    不是必须的,但是预热以后可以有效的防止因为流量洪峰导致的数据库出现问题。因为默认情况,仅仅有某条数据被读取一次,才会缓存在 innodb_buffer_pool。所以,数据库刚刚启动,最好进行进行数据预热,将磁盘上的全部数据缓存到内存中。数据预热能够提高读取速度。

  3. 降低磁盘写入次数

    • 增大redolog,减少落盘次数 innodb_log_file_size 设置为 0.25 * innodb_buffer_pool_size
    • 通用查询日志、慢查询日志可以不开 ,bin-log开。生产中不开通用查询日志,遇到性能问题开慢查询日志。
    • 写redolog策略 innodb_flush_log_at_trx_commit设置为0或2。如果不涉及非常高的安全性 (金融系统),或者基础架构足够安全,或者事务都非常小,都能够用 0 或者 2 来减少磁盘操作。
  4. 提高磁盘读写性能

    使用SSD或者内存磁盘

MySQL优化SQL语句的方法有哪些?​

  • 查看执行计划
  • SQL语句中IN包含的值不应过多
  • SELECT语句务必指明字段名称
  • 当只需要一条数据的时候,使用limit 1
  • 排序字段加索引
  • 如果限制条件中其他字段没有索引,尽量少用or
  • 尽量用union all代替union
  • 不使用ORDER BY RAND()
  • 区分in和exists、not in和not exists
  • 使用合理的分页方式以提高分页的效率
  • 分段查询
  • 不建议使用%前缀模糊查询
  • 避免在where子句中对字段进行表达式操作
  • 避免隐式类型转换
  • 对于联合索引来说,要遵守最左前缀法则
  • 必要时可以使用force index来强制查询走某个索引
  • 注意范围查询语句导致的索引失效
  • 使用JOIN优化,尽量用inner join替代left join

表结构设计优化有哪些手段?​

  • 设计中间表:一般针对于统计分析功能,或者实时性不高的需求(OLTP、OLAP)
  • 设计冗余字段:为减少关联查询,创建合理的冗余字段(创建冗余字段还需要注意数据一致性问题)
  • 拆表:对于字段太多的大表,考虑拆表,对于表中经常不被使用的字段或者存储数据比较多的字段,考虑拆表
  • 主键优化:每张表建议都要有一个主键(主键索引),而且主键类型最好是int类型,建议自增主键(不考虑分布式系统的情况)。
  • 字段的设计:数据库中的表越小,在它上面执行的查询也就会越快。因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。尽量把字段设置为NOTNULL,这样在将来执行查询的时候,数据库不用去比较NULL值。对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为ENUM类型。因为在MySQL中,ENUM类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。这样,我们又可以提高数据库的性能。

为什么要避免SQL的数据类型隐式转换?​

SQL数据类型的隐式转换会导致如下的问题:

  • 性能问题:隐式类型转换会导致数据库引擎无法使用索引,从而导致全表扫描,降低查询性能。
  • 结果不准确:隐式类型转换可能导致数据比较和计算结果不准确,尤其是在不同数据类型之间进行转换时。
  • 错误和异常:某些隐式转换可能会导致运行时错误或异常,特别是在转换失败的情况下。
  • 增加复杂性:隐式转换增加了 SQL 语句的复杂性,使得代码难以理解和维护。

常见的SQL数据类型隐式转换和解决方案?​

常见的隐式转换:

  • 字符串与数字之间的转换:

    SELECT * FROM employees WHERE emp_no = '12345';

    emp_no 列是数字类型,但查询条件是字符串。

  • 日期与字符串之间的转换:

    SELECT * FROM employees WHERE hire_date = '2023-10-01';

    hire_date 列是日期类型,但查询条件是字符串。

  • 布尔值与数字之间的转换:

    SELECT * FROM employees WHERE is_active = 1;

    is_active 列是布尔类型,但查询条件是数字。

  • 数字与浮点数之间的转换

    SELECT * FROM products WHERE price = 100.00;

    price 列是浮点数类型,但查询条件是整数。

避免隐式转换的方法

  • 显式转换:在 SQL 语句中使用显式类型转换函数,如 CAST 或 CONVERT。
    SELECT * FROM employees WHERE CAST(emp_no AS CHAR) = '12345';
  • 一致的数据类型:确保比较和计算的字段和常量具有一致的数据类型。
    SELECT * FROM employees WHERE emp_no = 12345;
  • 索引优化:确保索引列的数据类型与查询条件的数据类型一致。
    CREATE INDEX idx_emp_no ON employees(emp_no);
  • 使用参数化查询:在应用程序中使用参数化查询,确保传递的参数类型与数据库列类型一致。
    -- Example in Python with MySQL connector
    cursor.execute("SELECT * FROM employees WHERE emp_no = %s", (12345,))

数据库查询中count(*)和count(列名)有没有区别​

count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

MySQL为什么尽量使用inner join替代left join?​

参与联合查询的表至少为2张表,一般都存在大小之分。如果连接方式是inner join,在没有其他过滤条件的情况下MySQL会自动选择小表作为驱动表,但是left join在驱动表的选择上遵循的是左边驱动右边的原则,即left join左边的表名为驱动表。

SQL语句中IN包含的值不应过多​

MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。

执行DELETE/UPDATE时包含LIMIT​

执行DELETE或UPDATE语句时,建议添加LIMIT语句。示例如下:

-- Delete only one row from the 'employees' table with a specific condition
DELETE FROM employees WHERE department = 'Engineering' LIMIT 100;
  • 添加LIMIT子句有助于减少错误SQL语句的影响。如果在命令行中执行SQL命令时忘记添加LIMIT,可能会不小心删除所有数据。
  • 通过添加例如LIMIT 200,可以将潜在的损害限制在一定范围内,以便在意外删除时通过binlog日志恢复最多200条记录。
  • 在SQL查询中使用LIMIT 1可以提高效率,因为它在找到第一个匹配项后停止查询执行,避免了不必要的表扫描。
  • 此外,包含LIMIT子句有助于避免在DELETE等操作期间的长时间事务,特别是在处理大型数据集时,可以防止CPU过载和性能下降。

在INSERT语句中指定字段名​

这样做可以确保数据插入的准确性和健壮性。明确指定字段名能避免因表结构变动引发的错误,使得插入操作更加明确且易于维护,尤其是在表结构频繁调整的场景下。此外,这种方法还能提升代码的可读性,便于其他开发者理解插入操作的具体意图,以及在出现问题时快速定位和调试。

示例:

INSERT INTO Student (id, name, score) VALUES (123, 'user1', 100);

Update和Delete语句必须要指定Where条件​

执行Update和Delete语句时必须指定Where条件,是为了精确地定位并更改或删除数据库中指定的数据行,避免无差别地更改或删除所有数据,从而防止数据丢失或损坏,保证操作的准确性和安全性。同时可以减少数据库操作的范围,只对符合条件的数据行进行更新或删除。这样可以提高操作的执行效率,减少资源的消耗,提高操作的性能和效率。

示例:

-- Update the salary of the employee with ID 123
UPDATE employees SET salary = 60000 WHERE id = 123;

避免使用SELECT *​

使用 SELECT 特定字段只检索所需要数据有以下好处:

  • 提高查询性能:只查询需要的字段可以减少数据库的工作量和查询时间。数据库不需要检索和处理不相关的数据列,可以更快地返回结果。
  • 减少网络传输量:因为从数据库服务器到客户端只需要传输实际需要的数据,这可以显著减少数据传输时间和带宽消耗。
  • 降低内存消耗:查询结果中只包含需要的字段可以减少应用程序的内存消耗。如果返回的数据集较大,只保留必要的字段可以减少内存使用量,并降低应用程序的内存压力。

执行计划分析​

Explain执行计划分析里面每个列的含义​

  • id: 查询中每个 SELECT 子句的标识符。id 相同的行表示它们是同一个子查询或联合查询的一部分。

  • select_type: 查询的类型,如 SIMPLE(简单查询)、PRIMARY(主查询)、SUBQUERY(子查询)等。

  • table: 显示这一行的数据是关于哪张表的。

  • partitions: 匹配的分区信息。

  • type: 表示连接类型,性能从好到差依次为:system > const > eq_ref > ref > range > index > ALL。

  • possible_keys: 查询中可能使用的索引。

  • key: 实际使用的索引。

  • key_len: 使用的索引的长度。

  • ref: 显示索引的哪一列被使用了,如果可能的话,是哪一个常数。

  • rows: MySQL 估计为了找到所需的行而需要读取的行数。

  • filtered: 表示返回结果的行数占读取行数的百分比。

  • Extra: 额外的信息,如 Using index、Using where、Using temporary、Using filesort 等。

如何利用 Explain 进行 SQL 性能调优​

  • 检查 type 列: 确保查询至少达到 range 级别,最好是 ref 或 const 级别。避免出现 ALL 级别。

  • 使用合适的索引: 确保 key 列显示了实际使用的索引,并且 key_len 列显示了索引的长度。

  • 减少扫描行数: rows 列的值应尽可能小,表示扫描的行数少。

  • 优化 Extra 列: 避免出现 Using temporary 和 Using filesort,尽量使用覆盖索引(Using index)。

Explain 性能调优分析案例​

SELECT
e.emp_no, e.first_name, e.last_name, s.salary
FROM
employees e
JOIN
salaries s ON e.emp_no = s.emp_no
WHERE
e.hire_date > '2000-01-01'
AND
s.to_date = '9999-01-01'
ORDER BY
s.salary DESC;

执行计划分析得到的结果

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEsNULLrefemp_noemp_no4const1000100.00Using where; Using filesort
1SIMPLEeNULLeq_refPRIMARYPRIMARY4s.emp_no1100.00Using where

调优步骤:

  1. 检查 type 列: s 表的连接类型为 ref,e 表的连接类型为 eq_ref,都属于较好的连接类型。
  2. 优化索引: 确保 salaries 表的 emp_no 列和 employees 表的 hire_date 列有索引。
  3. 减少扫描行数: rows 列显示扫描的行数较多,可以通过优化索引减少扫描行数。
  4. 优化 Extra 列: 避免 Using filesort,可以通过索引优化排序。

优化后的 SQL 查询:

-- 为 salaries 表的 emp_no 和 to_date 列创建复合索引
CREATE INDEX idx_emp_no_to_date ON salaries(emp_no, to_date);

-- 为 employees 表的 hire_date 列创建索引
CREATE INDEX idx_hire_date ON employees(hire_date);

-- 优化后的查询
SELECT
e.emp_no, e.first_name, e.last_name, s.salary
FROM
employees e
JOIN
salaries s ON e.emp_no = s.emp_no
WHERE
e.hire_date > '2000-01-01'
AND
s.to_date = '9999-01-01'
ORDER BY
s.salary DESC;

执行优化后SQL的执行计划分析结果

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEsNULLrefemp_no, idx_emp_no_to_dateidx_emp_no_to_date8const500100.00Using where; Using index
1SIMPLEeNULLeq_refPRIMARY, idx_hire_datePRIMARY4s.emp_no1100.00Using where

结果分析:

  1. type 列显示连接类型为 ref 和 eq_ref,连接类型良好。
  2. rows 列显示扫描的行数减少,查询效率提高。
  3. Extra 列显示 Using index,避免了 Using filesort,进一步优化了查询性能。

Explain 执行计划中 Extra 列的调优建议​

在 Extra 列中,以下信息表示当前语句性能需要优化:

  • Using temporary: 表示查询使用了临时表,可能会导致性能问题。
  • Using filesort: 表示查询使用了文件排序,可能会导致性能问题。
  • Using where: 表示查询使用了 WHERE 子句进行过滤,可能需要优化索引。
  • Using join buffer: 表示查询使用了连接缓冲区,可能需要优化连接条件或索引。
  • Using index condition: 表示查询使用了索引条件,可能需要优化索引。
  • Using MRR: 表示查询使用了多范围读取,可能需要优化索引。
  • Using intersect: 表示查询使用了索引交集,可能需要优化索引。
  • Using union: 表示查询使用了索引并集,可能需要优化索引。

这些信息提示查询可能存在性能问题,需要进一步优化索引或查询结构

分库分表建议​

分库分表​

首先要清楚,分库和分表是两回事,是两个独立的概念。分库和分表都是为了防止数据库服务因为同一时间的访问量(增删查改)过大导致宕机而设计的一种应对策略。

为什么要分库

按一般的经验来说,一个单库最多支持并发量到2000,且最好保持在1000。如果有20000并发量的需求,这时就需要扩容了,可以将一个库的数据拆分到多个库中,访问的时候根据一定条件访问单库,缓解单库的性能压力。

为什么要分表

分表也是一样的,如果单表的数据量太大,就会影响SQL语句的执行性能。分表就是按照一定的策略将单表的数据拆分到多个表中,查询的时候也按照一定的策略去查询对应的表,这样就将一次查询的数据范围缩小了。比如按照用户id来分表,将一个用户的数据就放在一个表中,crud先通过用户id找到那个表在进行操作就可以了。这样就把每个表的数据量控制在一定范围内,提升SQL语句的执行性能。

MySQL如何分库分表​

垂直分库:微服务架构时,业务切割得足够独立,数据也会按照业务切分,保证业务数据隔离,大大提升了数据库的吞吐能力。

垂直分表:表中字段太多且包含大字段的时候,在查询时对数据库的IO、内存会受到影响,同时更新数据时,产生的binlog文件会很大,MySQL在主从同步时也会有延迟的风险。

水平分表:针对数据量巨大的单张表(比如订单表),按照规则把一张表的数据切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。

水平分库:将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。

4种分库分表规则​

  • 水平分库规则:不跨库、不跨表,保证同一类的数据都在同一个服务器上面。 数据在切分之前,需要考虑如何高效的进行数据获取,如果每次查询都要跨越多个节点,就需要谨慎使用。

  • 水平分表规则:

  • RANGE

    时间:按照年、月、日去切分。例如order_2020、order_202005、order_20200501

    地域:按照省或市去切分。例如order_beijing、order_shanghai、order_chengdu

    大小:从0到1000000一个表。例如1000001-2000000放一个表,每100万放一个表

  • HASH

    用户ID取模

    不同的业务使用的切分规则是不一样,就上面提到的切分规则,举例如下:

    【站内信】

    1. 用户维度:用户只能看到发送给自己的消息,其他用户是不可见的,这种情况下是按照用户ID hash分库,在用户查看历史记录翻页查询时,所有的查询请求都在同一个库内用户表
    2. 范围法:以用户ID为划分依据,将数据水平切分到两个数据库实例,如:1到1000W在一张表,1000W到2000W在一张表,这种情况会出现单表的负载较高按照用户ID HASH尽量保证用户数据均衡分到数据库中如果在登录场景下,用户输入手机号和验证码进行登录,这种情况下,登录时是不是需要扫描所有分库的信息?
    3. 最终方案:用户信息采用ID做切分处理,同时存储用户ID和手机号的映射的关系表(新增一个关系表),关系表采用手机号进行切分。可以通过关系表根据手机号查询到对应的ID,再定位用户信息。

    【流水表】

    时间维度:可以根据每天新增的流水来判断,选择按照年份分库,还是按照月份分库, 甚至也可以按照日期分库订单表在拉勾网,求职者(下面统称C端用户)投递企业(下面统称B端用户)的职位产生的记录称之为订单表。在线上的业务场景中,C端用户看自己的投递记录,每次的投递到了哪个状态,B端用户查看自己收到的简历,对于合适的简历会进行下一步沟通,同一个公司内的员工可以协作处理简历

对数据库进行垂直拆分或水平拆分​

水平拆分的概念

水平拆分的意思,就是把一个表的数据拆分到多个库的多个表里面去。这里面的每个库的表结构都是一样的,只不过是表中存放的数据不一样,每个库表的数据汇总起来就是全部数据。水平拆分的意义在于将数据均匀地存放在各个库表里,依靠多个库来杠更高的并发,而且还能借助多个库的存储容量来进行扩容。

垂直拆分的概念

垂直拆分的意思,就是把一个有很多字段的表给拆分成多个表或者多个库上面去,每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较少的访问频率很高的字段放到一个表里面去,然后将较多的访问频率很低的字段放到另外一个表里面去。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里面缓存更多的行,性能也就越好。这个一般在表层面做的较多一些。

水平拆分和垂直拆分的场景

所谓表层面的拆分,就是分表。具体就是将一个表拆分为N个表,让每个表的数据量控制在一定的范围内,保证SQL的性能。否则,单表的数据量越大,SQL的性能也就越差,一般是200万行左右,不要太多。如果你的SQL越复杂,就尽量让单表的行数越少。

无论是分库还是分表,主流的数据库中间件都是可以支持的。这些中间件可以在你分库分表之后,根据指定的某个字段值自动路由到对应的库和对应的表上面。这时就只要考虑项目如何分库分表就行了。一般来说,垂直拆分,可以在表层面做,即对一些字段特别多的表做一下拆分;水平拆分的话,可能是因为并发承载不了或容量承载不了,也就可以按某个字段去分布到不同的库表里面去。

分库分表的两个方案​

这里说一下两种分库分表的方案和它们的优缺点。

1.按照range来分。比如说按照时间范围来分库分表,每个库表中存放的都是连续时间范围的数据。但是这种方式一般很少用,因为很容易会产生热点问题,大量的流量都打在最新的数据上了。这种方案的优点在于扩容的时候非常简单,比如只要预备好每个月都准备一个库就可以了,到了下一个新的月份自动将数据写入新的库。缺点则是,如果大部分请求都是访问最新的数据,那么在这里,分库分表的设计目的就只是简单的扩容,而不是为了应对高并发了。

2.按照hash分发。按照某个字段的hash值均匀分散,这个较为常用。优点在于可以平均分配每个库表的数据量和请求压力;缺点在于扩容比较麻烦,因为会存在一个数据迁移的过程,即之前的数据需要重新计算hash值并重新分配到不同的库表中。

其它建议建议​

冷库启动时要数据预热​

默认情况,仅仅有某条数据被读取一次,才会缓存在 innodb_buffer_pool。所以,数据库刚刚启动,须要进行数据预热,将磁盘上的全部数据缓存到内存中。

数据预热能够提高读取速度。

  1. 对于InnoDB数据库,进行数据预热的脚本是:
SELECT DISTINCT
CONCAT('SELECT ',ndxcollist,' FROM ',db,'.',tb,
' ORDER BY ',ndxcollist,';') SelectQueryToLoadCache
FROM
(
SELECT
engine,table_schema db,table_name tb,
index_name,GROUP_CONCAT(column_name ORDER BY seq_in_index)
ndxcollist
FROM
(
SELECT
B.engine,A.table_schema,A.table_name,
A.index_name,A.column_name,A.seq_in_index
FROM
information_schema.statistics A INNER JOIN
(
SELECT engine,table_schema,table_name
FROM information_schema.tables WHERE
engine='InnoDB'
) B USING (table_schema,table_name)
WHERE B.table_schema NOT IN ('information_schema','mysql')
ORDER BY table_schema,table_name,index_name,seq_in_index
) A
GROUP BY table_schema,table_name,index_name
) AA
ORDER BY db,tb;

将该脚本保存为:loadtomem.sql

2、执行命令:

mysql -uroot -proot -AN < /root/loadtomem.sql > /root/loadtomem.sql

3、在需要数据预热时,比如重启数据库 执行命令:

mysql -uroot < /root/loadtomem.sql > /dev/null 2>&1

使用InnoDB存储引擎​

InnoDB是一种流行的MySQL存储引擎,它被广泛使用,主要因为它具有以下优点:

  • 事务支持:InnoDB是一个支持事务的存储引擎,它提供了ACID(原子性、一致性、隔离性和持久性)特性。这意味着你可以在InnoDB表上执行复杂的事务操作,确保数据的一致性和完整性。
  • 行级锁:与表级锁定相比,InnoDB的行级锁定可以提供更好的并发性能,因为它只锁定数据行而不是整个表。
  • 外键约束:InnoDB支持外键约束,可以在数据库层面实现关联表之间的数据完整性和一致性。外键可以帮助维护表与表之间的关系,并防止不一致或无效的数据关联。
  • 并发控制:InnoDB使用多版本并发控制(MVCC)来处理并发访问。这意味着它可以在不同的事务之间提供高度的隔离性,避免数据的冲突和锁竞争。
  • 崩溃恢复:InnoDB提供了崩溃恢复机制,可以在数据库发生故障或断电后自动进行恢复。

示例:

CREATE TABLE users (
id INT AUTOINCREMENT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100)
) ENGINE=InnoDB;

CREATE TABLE orders (
id INT AUTOINCREMENT PRIMARY KEY,
userid INT,
amount DECIMAL(10, 2),
createdat TIMESTAMP,
FOREIGN KEY (userid) REFERENCES users(id)
) ENGINE=InnoDB;

将UTF8MB4作为数据库和表的字符集​

设计数据库中的库和表时选择UTF8MB4作为字符集,主要原因如下:

  • 国际化支持:UTF8MB4字符集能够支持全球大多数语言的文字,包括各种特殊字符、表情符号等,这使得应用能够无障碍地服务于多语言环境下的用户。
  • 兼容性:UTF8MB4是UTF8的超集,它向下兼容UTF8,意味着使用UTF8MB4不会影响原有的UTF8数据,同时还能支持4字节的Unicode字符,确保了数据的完整性和广泛适用性。
  • 未来扩展性:UTF8是一种可扩展的字符集,它支持Unicode编码,并具备适应未来字符需求的能力。

表中必须包含主键、创建时间、更新时间字段​

这些字段在数据库设计中是常用的最佳实践,有助于提高数据的可靠性、可维护性和可追溯性。

  • 主键:主键是用于唯一标识表中每个记录的字段。它确保了每条记录的唯一性,并为数据库提供了快速的数据定位和索引。主键还用于建立数据表之间的关联关系,实现数据的一致性和完整性。
  • 创建时间:创建时间字段记录了每条记录的插入时间。它对于跟踪数据的创建顺序和时间线非常有用。创建时间可以用于数据分析、审计和排查问题,还可以帮助识别过时的数据或定期清理不再需要的数据。
  • 更新时间:更新时间字段记录了每条记录的最后一次更新时间。它对于追踪数据的变更、维护数据的一致性和追溯数据流动非常重要。更新时间可以用于监控数据的变化、检测数据的实效性,并支持数据恢复和修复操作。

示例:

CREATE TABLE `exampletable` (
`id` INT AUTOINCREMENT PRIMARY KEY,
`name` VARCHAR(255),
`createtime` TIMESTAMP DEFAULT CURRENTTIMESTAMP,
`updatetime` TIMESTAMP DEFAULT CURRENTTIMESTAMP ON UPDATE CURRENTTIMESTAMP
);

先在测试环境中验证SQL​

在执行任何SQL之前,先在测试环境中验证一次,然后再到生产环境中执行;这是因为:

  • 安全性:避免直接在生产环境执行可能影响数据完整性和安全性的SQL语句。
  • 错误检测:在测试环境中可以发现并修复SQL语句中的错误或性能问题,减少在生产环境中出现问题的风险。
  • 性能评估:测试环境中可以模拟生产环境的数据量和使用情况,评估SQL语句的性能,确保其在生产环境中的效率。
  • 影响控制:测试环境通常对业务影响较小,可以控制SQL变更的影响范围,避免对生产环境造成不必要的中断或损失。

为表和字段添加适当的注释​

设计数据库表时,养成为所有表和字段添加适当注释的习惯。 这种做法有助于未来维护,为数据库结构提供清晰的上下文。

示例如下:

CREATE TABLE `account` (
`id` int(11) NOT NULL AUTOINCREMENT COMMENT '主键',
`name` varchar(255) DEFAULT NULL COMMENT '账户名称',
`balance` int(11) DEFAULT NULL COMMENT '账户余额',
`createtime` datetime NOT NULL COMMENT '创建时间',
`updatetime` datetime NOT NULL ON UPDATE CURRENTTIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idxname` (`name`) USING BTREE
) ENGINE=InnoDB AUTOINCREMENT=1570068 DEFAULT CHARSET=utf8 ROWFORMAT=REDUNDANT COMMENT='账户信息表';

关键字大写并使用缩进​

SQL书写规范中强调关键字大写和使用缩进,是为了提升代码的可读性和整洁度。关键字大写使得关键词更加醒目,便于快速识别,而缩进则清晰展现了语句结构,有助于理解代码逻辑,两者结合极大地增强了代码的可维护性和团队协作效率。

正例:

SELECT stu.name, SUM(stu.score)
FROM Student AS stu
WHERE stu.classNo = '10'
GROUP BY stu.name;

反例:

SELECT stu.name, sum(stu.score) from Student stu WHERE stu.classNo = '10' group by stu.name.