数据库开发原则
一、核心原则
- 
    
不在数据库做运算
- 尽量不在数据库做运算
 - 复杂运算移到程序端 CPU
 - 尽可能简单应用 MYSQL
 
 - 
    
控制单表数据量
- 纯 INT 不超过 1000W
 - 含 CHAR 不超过 500W
 
 - 
    
控制列数量
- 单表不超 50 个纯 INT 字段
 - 单表不超 20 个 CHAR(10)字段
 - IO 高效
 - 全表遍历
 - 表修复快
 - 提高并发
 - alter table 更快
 
 - 
    
平衡范式与冗余
- 第一范式:单个字段不可再分。唯一性。
 - 第二范式:不存在非主属性只依赖部分主键。消除不完全依赖。
 - 第三范式:消除传递依赖。
 - 用一句话来总结范式和冗余:
        
- 冗余是以存储换取性能,
 - 范式是以性能换取存储。
 
 
 - 
    
拒绝 3B
- 大 SQL(BIG SQL):要减少
 - 大事务(BIG Transaction)
 - 大批量(BIG Batch)
 
 
二、字段类原则
- 
    
用好数值字段类型
- 有符号 int 最大可以支持到约 22 亿,远远大于我们的需求和 MySQL 单表所能支持的性能上限。对于 OLTP 应用来说,单表的规模一般要保持在千万级别,不会达到 22 亿上限。如果要加大预留量,可以把主键改为改为无符号 int,上限为 42 亿,这个预留量已经是非常的充足了。
 - 使用 bigint,会占用更大的磁盘和内存空间,内存空间毕竟有限,无效的占用会导致更多的数据换入换出,额外增加了 IO 的压力,对性能是不利的。
 
 - 
    
将字符转化为数字
- 更高效
 - 查询更快
 - 占用空间更小
 - 举例: 用无符号 INT 存储 IP,而非 CHAR(15) INT UNSIGNED 可以用 INET_ATON()和 INET_NTOA()来实现 IP 字符串和数值之间的转换
 
 - 
    
优先使用 ENUM 或 SET
对于一些枚举型数据,推荐优先使用 ENUM 或 SET,这样的场景适合:
- 字符串型
 - 可能值已知且有限
 
存储方面:
- ENUM 占用 1 字节,转为数值运算
 - SET 视节点定,最多占用 8 字节
 - 比较时需要加‘单引号(即使是数值)
 
举例:
`sex` enum('F','M') COMMENT '性别'; `c1` enum('0','1','2','3') COMMENT '审核'; - 
    
避免使用 NULL 字段
为什么在数据库表字段设计的时候尽量都加上 NOT NULL DEFAULT ‘‘,这里面不得不说用 NULL 字段的弊端
- 很难进行查询优化
 - NULL 列加索引,需要额外空间
 - 含 NULL 复合索引无效
        
- 举例:
            
achar(32) DEFAULT NULL 【不推荐】bint(10) NOT NULL 【不推荐】cint(10) NOT NULL DEFAULT 0 【推荐】
 
 - 举例:
            
 
 - 
    
少用并拆分 TEXT/BLOB
- TEXT 类型处理性能远低于 VARCHAR
 - 强制生成硬盘临时表
 - 浪费更多空间
 - VARCHAR(65535)==>64K(注意 UTF-8)
 - 尽量不用 TEXT/BLOB 数据类型
 - 如果业务需要必须用,建议拆分到单独的表
 
 - 
    
不在数据库里存图片
- 将图片全部存在数据库,将使得数据库体积变大,会造成读写速度变慢。
 - 对数据库的读/写的速度永远都赶不上文件系统处理的速度
 - 数据库备份变的巨大,越来越耗时间
 - 对文件的访问需要穿越你的应用层和数据库层
 - 推荐处理办法:数据库中保存图片路径
 
 
三、索引类原则
- 
    
谨慎合理添加索引
- 添加索引是为了改善查询
 - 添加索引会减慢更新
 - 索引不是越多越好
 - 能不加的索引尽量不加(综合评估数据密度和数据分布,最好不超过字段数 20%)
 - 结合核心 SQL 有限考虑覆盖索引
 
 - 
    
字符字段必须建前缀索引 区分度:
- 单字母区分度:26
 - 4 字母区分度:262626*26 = 456,976
 - 5 字母区分度:2626262626 = 11,881,376
 - 6 字母区分度:2626262626*26 = 308,915,776
 - 字符字段必须建前缀索引,例如:
        
`pinyin` varchar(100) DEFAULT NULL COMMENT '小区拼音', KEY `idx_pinyin` (`pinyin`(8)), ) ENGINE=InnoDB 
 - 
    
不在索引列做运算
- 会导致无法使用索引
 - 会导致全表扫描
 
 - 
    
自增列或全局 ID 做 INNODB 主键
- 对主键建立聚簇索引
 - 二级索引存储主键值
 - 主键不应更新修改
 - 按自增顺序插入值
 - 忌用字符串做主键
 - 聚簇索引分裂
 - 推荐用独立于业务的 AUTO_INCREMENT 列或全局 ID 生成器做代理主键
 - 若不指定主键,InnoDB 会用唯一且非空值索引代替
 
 - 
    
尽量不用外键 线上 OLTP 系统尽量不用外键:
- 外键可节省开发量
 - 有额外开销
 - 逐行操作
 - 可“到达”其他表,意味着锁
 - 高并发时容易死锁
 
 
四、SQL 类原则
- 
    
SQL 语句尽可能简单
- 一条 SQL 只能在一个 CPU 运算
 - 拒绝大 SQL,拆解成多条简单 SQL
        
- 简单 SQL 缓存命中率更高
 - 减少锁表时间,特别是 MyISAM
 - 用上多 CPU
 
 
 - 
    
保持事务(连接)短小
- 事务/连接使用原则:即开即用,用完即关
 - 与事务无关操作都放到事务外面,减少锁资源的占用
 - 不破坏一致性前提下,使用多个短事务代替长事务
 - 举例:
        
- 发帖时的图片上传等待
 - 大量的 sleep 连接
 
 
 - 
    
尽可能避免使用 SP/TRIG/FUNC
- 线上 OLTP 系统中,我们应当:
        
- 尽可能少用存储过程
 - 尽可能少用触发器
 - 减少使用 MySQL 函数对结果进行处理
 - 将上述这些事情都交给客户端程序负责
 
 
 - 线上 OLTP 系统中,我们应当:
        
 - 
    
尽量不用 SELECT *
- 用 SELECT * 时,将会更多的消耗 CPU、内存、IO 以及网络带宽
 - 我们在写查询语句时,应当尽量不用 SELECT * ,只取需要的数据列:
        
- 更安全的设计:减少表变化带来的影响
 - 为使用 covering index 提供可能性
 - Select/JOIN 减少硬盘临时表生成,特别是有 TEXT/BLOB 时
 
 
 - 改写 OR 为 IN()
    
- 同一字段,将 or 改写为 in()
        
- OR 效率:O(n)
 - IN 效率:O(Log n)
 
 - 举例:
        
- 不推荐:
            
Select * from opp WHERE phone='12347856' or phone='42242233' - 推荐:
            
Select * from opp WHERE phone in ('12347856' , '42242233') 
 - 不推荐:
            
 
 - 同一字段,将 or 改写为 in()
        
 - 
    
改写 OR 为 UNION
- 不同字段,将 or 改为 union
        
- 减少对不同字段进行 “or” 查询
 - Merge index 往往很弱智
 - 如果有足够信心:set global optimizer_switch=’index_merge=off’;
 
 - 举例:
        
- 不推荐:
            
Select * from opp WHERE phone='010-88886666' or cellPhone='13800138000'; - 推荐:
            
Select * from opp WHERE phone='010-88886666' union Select * from opp WHERE cellPhone='13800138000'; 
 - 不推荐:
            
 
 - 不同字段,将 or 改为 union
        
 - 
    
避免负向查询和%前缀模糊查询
- 在实际开发中,我们要尽量避免负向查询,那什么是负向查询呢,主要有以下:
        
- NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE 等
 
 - 同时,我们还要避免%前缀模糊查询,因为这样会使用 B+ Tree,同时会造成使用不了索引,并且会导致全表扫描,性能和效率可想而知
 
 - 在实际开发中,我们要尽量避免负向查询,那什么是负向查询呢,主要有以下:
        
 - 
    
LIMIT 高效分页
#先使用程序获取ID: select id from table limit 10000,10; #再用in获取ID对应的记录 Select * from table WHERE id in (123,456…) ; - 
    
用 UNION ALL 而非 UNION
- 如果无需对结果进行去重,仅仅是对多表进行联合查询并展示,则用 UNION ALL,因为 UNION 有去重开销
 - 举例:
        
SELECT * FROM detail20091128 UNION ALL SELECT * FROM detail20110427 UNION ALL SELECT * FROM detail20110426 UNION ALL SELECT * FROM detail20110425 UNION ALL SELECT * FROM detail20110424 UNION ALL SELECT * FROM detail20110423; 
 - 
    
分解联接保证高并发
- 高并发 DB 不建议进行两个表以上的 JOIN
 - 适当分解联接保证高并发:
        
- 可缓存大量早期数据
 - 使用了多个 MyISAM 表
 - 对大表的小 ID IN()
 - 联接引用同一个表多次
 
 
 - 
    
GROUP BY 去除排序
- 使用 GROUP BY 可以实现分组和自动排序
 - 无需排序:Order by NULL
 - 特定排序:Group by DESC/ASC
 
 - 
    
打散大批量更新
- 大批量更新尽量凌晨操作,避开高峰
 - 凌晨不限制
 - 白天上线默认为 100 条/秒(特殊再议)
 - 举例:
        
update post set tag=1 WHERE id in (1,2,3); sleep 0.01; update post set tag=1 WHERE id in (4,5,6); sleep 0.01; …… 
 - Know Every SQL
    
- 作为 DBA 乃至数据库开发人员,我们必须对数据库的每条 SQL 都非常了解,常见的命令有:
        
- SHOW PROFILE
 - MYSQLsla
 - MySQLdumpslow
 - explain
 - Show Slow Log
 - Show Processlist
 - SHOW QUERY_RESPONSE_TIME(Percona)
 
 
 - 作为 DBA 乃至数据库开发人员,我们必须对数据库的每条 SQL 都非常了解,常见的命令有:
        
 
五、约定类原则
- 
    
隔离线上线下
- 构建数据库的生态环境,确保开发无线上库操作权限
 - 原则:线上连线上,线下连线下
        
- 生产数据用 pro 库
 - 预生产环境用 pre 库
 - 测试用 test 库
 - 开发用 dev 库
 
 
 - 
    
禁止未经 DBA 确认的子查询
- 大部分情况优化较差
 - 特别 WHERE 中使用 IN id 的子查询
 - 一般可用 JOIN 改写
 - 举例:
        
MySQL> select * from table1 where id in (select id from table2); MySQL> insert into table1 (select * from table2); //可能导致复制异常