MySQL 优化建议
SQL语句及索引优化建议
MySQL数据库优化手段优先级
- SQL语句及索引优化 -> 表结构设计 -> 系统配置 -> 硬件升级
- 优化成本:硬件升级>系统配置>表结构设计>SQL语句及索引。
- 优化效果:硬件升级<系统配置<表结构设计<SQL语句及索引。
数据库哪些情况需要建索引
- 主键自动建立唯一索引
- 频繁作为查询的条件的字段应该创建索引
- 查询中与其他表关联的字段,外键关系建立索引
- 频繁更新的字段不适合创建索引:因为每次更新不单单是更新了记录还会更新索引,加重IO负担
- Where条件里用不到的字段不创建索引
- 单间/组合索引的选择问题(在高并发下倾向创建组合索引)
- 查询中排序的字段,若通过索引去访问将大大提高排序的速度
- 查询中统计或者分组字段
MySQL优化核心关注点有哪些?
- 开启慢查询日志,定位运行慢的SQL语句
- 利用explain执行计划,查看SQL执行情况
- 关注索引使用情况:type
- 关注Rows:行扫描
- 关注Extra:没有信息最好
- 加索引后,查看索引使用情况,index只是覆盖索引,并不算很好的使用索引
- 如果有关联尽量将索引用到eq_ref或ref级别
- 复杂SQL可以做成视图,视图在MySQL内部有优化,而且开发也比较友好
- 对于复杂的SQL要逐一分析,找到比较费时的SQL语句片段进行优化
更多细节请查看后续 《执行计划分析》章节的内容
MySQL系统配置优化的主要目的是什么?
-
保证从内存中读取数据
MySQL会在内存中保存一定的数据,通过LRU算法将不常访问的数据保存在硬盘文件中。尽可能的扩大内存中的数据量,将数据保存在内存中,从内存中读取数据,可以提升MySQL性能。扩大innodb_buffer_pool_size,能够全然从内存中读取数据。最大限度降低磁盘操作。
-
进行数据预热
不是必须的,但是预热以后可以有效的防止因为流量洪峰导致的数据库出现问题。因为默认情况,仅仅有某条数据被读取一次,才会缓存在 innodb_buffer_pool。所以,数据库刚刚启动,最好进行进行数据预热,将磁盘上的全部数据缓存到内存中。数据预热能够提高读取速度。
-
降低磁盘写入次数
- 增大redolog,减少落盘次数 innodb_log_file_size 设置为 0.25 * innodb_buffer_pool_size
- 通用查询日志、慢查询日志可以不开 ,bin-log开。生产中不开通用查询日志,遇到性能问题开慢查询日志。
- 写redolog策略 innodb_flush_log_at_trx_commit设置为0或2。如果不涉及非常高的安全性 (金融系统),或者基础架构足够安全,或者事务都非常小,都能够用 0 或者 2 来减少磁盘操作。
-
提高磁盘读写性能
使用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 connectorcursor.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;
执行计划分析得到的结果
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | s | NULL | ref | emp_no | emp_no | 4 | const | 1000 | 100.00 | Using where; Using filesort |
| 1 | SIMPLE | e | NULL | eq_ref | PRIMARY | PRIMARY | 4 | s.emp_no | 1 | 100.00 | Using where |
调优步骤:
- 检查 type 列: s 表的连接类型为 ref,e 表的连接类型为 eq_ref,都属于较好的连接类型。
- 优化索引: 确保 salaries 表的 emp_no 列和 employees 表的 hire_date 列有索引。
- 减少扫描行数: rows 列显示扫描的行数较多,可以通过优化索引减少扫描行数。
- 优化 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的执行计划分析结果
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | s | NULL | ref | emp_no, idx_emp_no_to_date | idx_emp_no_to_date | 8 | const | 500 | 100.00 | Using where; Using index |
| 1 | SIMPLE | e | NULL | eq_ref | PRIMARY, idx_hire_date | PRIMARY | 4 | s.emp_no | 1 | 100.00 | Using where |
结果分析:
- type 列显示连接类型为 ref 和 eq_ref,连接类型良好。
- rows 列显示扫描的行数减少,查询效率提高。
- 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取模
不同的业务使用的切分规则是不一样,就上面提到的切分规则,举例如下:
【站内信】
- 用户维度:用户只能看到发送给自己的消息,其他用户是不可见的,这种情况下是按照用户ID hash分库,在用户查看历史记录翻页查询时,所有的查询请求都在同一个库内用户表
- 范围法:以用户ID为划分依据,将数据水平切分到两个数据库实例,如:1到1000W在一张表,1000W到2000W在一张表,这种情况会出现单表的负载较高按照用户ID HASH尽量保证用户数据均衡分到数据库中如果在登录场景下,用户输入手机号和验证码进行登录,这种情况下,登录时是不是需要扫描所有分库的信息?
- 最终方案:用户信息采用ID做切分处理,同时存储用户ID和手机号的映射的关系表(新增一个关系表),关系表采用手机号进行切分。可以通过关系表根据手机号查询到对应的ID,再定位用户信息。
【流水表】
时间维度:可以根据每天新增的流水来判断,选择按照年份分库,还是按照月份分库, 甚至也可以按照日期分库订单表在拉勾网,求职者(下面统称C端用户)投递企业(下面统称B端用户)的职位产生的记录称之为订单表。在线上的业务场景中,C端用户看自己的投递记录,每次的投递到了哪个状态,B端用户查看自己收到的简历,对于合适的简历会进行下一步沟通,同一个公司内的员工可以协作处理简历
对数据库进行垂直拆分或水平拆分
水平拆分的概念
水平拆分的意思,就是把一个表的数据拆分到多个库的多个表里面去。这里面的每个库的表结构都是一样的,只不过是表中存放的数据不一样,每个库表的数据汇总起来就是全部数据。水平拆分的意义在于将数据均匀地存放在各个库表里,依靠多个库来杠更高的并发,而且还能借助多个库的存储容量来进行扩容。
垂直拆分的概念
垂直拆分的意思,就是把一个有很多字段的表给拆分成多个表或者多个库上面去,每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会将较少的访问频率很高的字段放到一个表里面去,然后将较多的访问频率很低的字段放到另外一个表里面去。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里面缓存更多的行,性能也就越好。这个一般在表层面做的较多一些。
水平拆分和垂直拆分的场景
所谓表层面的拆分,就是分表。具体就是将一个表拆分为N个表,让每个表的数据量控制在一定的范围内,保证SQL的性能。否则,单表的数据量越大,SQL的性能也就越差,一般是200万行左右,不要太多。如果你的SQL越复杂,就尽量让单表的行数越少。
无论是分库还是分表,主流的数据库中间件都是可以支持的。这些中间件可以在你分库分表之后,根据指定的某个字段值自动路由到对应的库和对应的表上面。这时就只要考虑项目如何分库分表就行了。一般来说,垂直拆分,可以在表层面做,即对一些字段特别多的表做一下拆分;水平拆分的话,可能是因为并发承载不了或容量承载不了,也就可以按某个字段去分布到不同的库表里面去。
分库分表的两个方案
这里说一下两种分库分表的方案和它们的优缺点。
1.按照range来分。比如说按照时间范围来分库分表,每个库表中存放的都是连续时间范围的数据。但是这种方式一般很少用,因为很容易会产生热点问题,大量的流量都打在最新的数据上了。这种方案的优点在于扩容的时候非常简单,比如只要预备好每个月都准备一个库就可以了,到了下一个新的月份自动将数据写入新的库。缺点则是,如果大部分请求都是访问最新的数据,那么在这里,分库分表的设计目的就只是简单的扩容,而不是为了应对高并发了。
2.按照hash分发。按照某个字段的hash值均匀分散,这个较为常用。优点在于可以平均分配每个库表的数据量和请求压力;缺点在于扩容比较麻烦,因为会存在一个数据迁移的过程,即之前的数据需要重新计算hash值并重新分配到不同的库表中。
其它建议建议
冷库启动时要数据预热
默认情况,仅仅有某条数据被读取一次,才会缓存在 innodb_buffer_pool。所以,数据库刚刚启动,须要进行数据预热,将磁盘上的全部数据缓存到内存中。
数据预热能够提高读取速度。
- 对于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.