数据库服务衡量标准

  • Qps : query per second

  • Tps : transaction per second

1. 压力测试工具

1.1 常见MySQL压力测试工具

1.2 mysqlslap

mysqlslap : 来自于mariadb包 , 测试的过程默认生成一个mysqlslapschema,生成测试表t1 , 查询和 插入测试数据 , mysqlslap库自动生成 , 如果已经存在则先删除。用--only-print来打印实际的测试过 程 , 整个测试完成后不会在数据库中留下痕迹

使用格式: mysqlslap [options]

常用参数 [options] 说明:

  • --auto-generate-sql, -a : 自动生成测试表和数据 , 表示用mysqlslap工具自己生成的SQL脚本来测试 并发压力

  • --auto-generate-sql-load-type=type : 测试语句的类型。代表要测试的环境是读操作还是写操作还 是两者混合的。取值包括 : read , key , write , updatemixed(默认)

  • --auto-generate-sql-add-auto-increment : 代表对生成的表自动添加auto_increment列 , 从 5.1.18版本开始支持

  • --number-char-cols=N, -x N : 自动生成的测试表中包含多少个字符类型的列 , 默认1

  • --number-int-cols=N, -y N : 自动生成的测试表中包含多少个数字类型的列 , 默认1

  • --number-of-queries=N : 总的测试查询次数(并发客户数×每客户查询次数)

  • --query=name,-q : 使用自定义脚本执行测试 , 例如可以调用自定义的存储过程或者sql语句来执行测试

  • --create-schema : 代表自定义的测试库名称 , 测试的schema

  • --commint=N : 多少条DML后提交一次

  • --compress, -C : 如服务器和客户端都支持压缩 , 则压缩信息

  • --concurrency=N, -c N : 表示并发量 , 即模拟多少个客户端同时执行select。可指定多个值 , 以逗号 或者--delimiter参数指定值做为分隔符,如 : --concurrency=100,200,500

  • --engine=engine_name, -e engine_name : 代表要测试的引擎 , 可以有多个 , 用分隔符隔开。例 如 : --engines=myisam,innodb

  • --iterations=N, -i N : 测试执行的迭代次数 , 代表要在不同并发环境下 , 各自运行测试多少次

  • --only-print : 只打印测试语句而不实际执行。

  • --detach=N : 执行N条语句后断开重连

  • --debug-info, -T : 打印内存和CPU的相关信息

mysqlslap示例

#单线程测试
mysqlslap -a -uroot -pmagedu
​
#多线程测试。使用--concurrency来模拟并发连接
mysqlslap -a -c 100 -uroot -pmagedu
​
#迭代测试。用于需要多次执行测试得到平均值
mysqlslap -a -i 10 -uroot -pmagedu
mysqlslap ---auto-generate-sql-add-autoincrement -a
mysqlslap -a --auto-generate-sql-load-type=read
mysqlslap -a --auto-generate-secondary-indexes=3
mysqlslap -a --auto-generate-sql-write-number=1000
mysqlslap --create-schema world -q "select count(*) from City”
mysqlslap -a -e innodb -uroot -pmagedu
mysqlslap -a --number-of-queries=10 -uroot -pmagedu
​
#测试同时不同的存储引擎的性能进行对比
mysqlslap -a --concurrency=50,100 --number-of-queries 1000 --iterations=5 --engine=myisam,innodb --debug-info -uroot -pmagedu
​
#执行一次测试 , 分别50和100个并发 , 执行1000次总查询
mysqlslap -a --concurrency=50,100 --number-of-queries 1000 --debug-info -uroot -pmagedu
​
#50和100个并发分别得到一次测试结果(Benchmark) , 并发数越多 , 执行完所有查询的时间越长。为了准确起见 , 可以多迭代测试几次
mysqlslap -a --concurrency=50,100 --number-of-queries 1000 --iterations=5 --debug-info -uroot -pmagedu

2. 生产环境 my.cnf 配置案例

配置文件生成工具参考链接 : https://imysql.com/my_cnf_generator

参考硬件 : 内存32G

#打开独立表空间
innodb_file_per_table = 1
#MySQL 服务所允许的同时会话数的上限 , 经常出现Too Many Connections的错误提示 , 则需要增大此值
max_connections = 8000 
#所有线程所打开表的数量
open_files_limit = 10240
#back_log 是操作系统在监听队列中所能保持的连接数
back_log = 300
#每个客户端连接最大的错误允许数量 , 当超过该次数 , MYSQL服务器将禁止此主机的连接请求 , 直到MYSQL服务器重启或通过flush hosts命令清空此主机的相关信息
max_connect_errors = 1000
#每个连接传输数据大小.最大1G , 须是1024的倍数 , 一般设为最大的BLOB的值
max_allowed_packet = 32M
#指定一个请求的最大连接时间
wait_timeout = 10
# 排序缓冲被用来处理类似ORDER BY以及GROUP BY队列所引起的排序
sort_buffer_size = 16M 
#不带索引的全表扫描.使用的buffer的最小值
join_buffer_size = 16M 
#查询缓冲大小
query_cache_size = 128M
#指定单个查询能够使用的缓冲区大小 , 缺省为1M
query_cache_limit = 4M    
#设定默认的事务隔离级别
transaction_isolation = REPEATABLE-READ 
#线程使用的堆大小. 此值限制内存中能处理的存储过程的递归深度和SQL语句复杂性 , 此容量的内存在每次连接时被预留.
thread_stack = 512K 
#二进制日志功能
log-bin=/data/mysqlbinlogs/
#二进制日志格式
binlog_format=row
#InnoDB使用一个缓冲池来保存索引和原始数据, 可设置这个变量到物理内存大小的80%
innodb_buffer_pool_size = 24G 
#用来同步IO操作的IO线程的数量
innodb_file_io_threads = 4
#在InnoDb核心内的允许线程数量 , 建议的设置是CPU数量加上磁盘数量的两倍
innodb_thread_concurrency = 16
# 用来缓冲日志数据的缓冲区的大小
innodb_log_buffer_size = 16M
#在日志组中每个日志文件的大小
innodb_log_file_size = 512M 
#在日志组中的文件总数
innodb_log_files_in_group = 3
#SQL语句在被回滚前,InnoDB事务等待InnoDB行锁的时间
innodb_lock_wait_timeout = 120
#慢查询时长
long_query_time = 2
#将没有使用索引的查询也记录下来
log-queries-not-using-indexes

3. MySQL配置最佳实践

高并发大数据的互联网业务 , 架构设计思路是“解放数据库CPU , 将计算转移到服务层” , 并发量大的情况下 , 这些功能很可能将数据库拖死 , 业务逻辑放到服务层具备更好的扩展性 , 能够轻易实现“增机器 就加性能”

参考资料 :

以下规范适用场景: 并发量大、数据量大的互联网业务

3.1 基础规范

  1. 必须使用InnoDB存储引擎

    解读: 支持事务、行级锁、并发性能更好、CPU及内存缓存页优化使得资源利用率更高

  2. 使用UTF8MB4字符集

    解读: 万国码 , 无需转码 , 无乱码风险 , 节省空间 , 支持表情包及生僻字

  3. 数据表、数据字段必须加入中文注释

    解读: N年后谁知道这个r1,r2,r3字段是干嘛的

  4. 禁止使用存储过程、视图、触发器、Event

    解读: 高并发大数据的互联网业务 , 架构设计思路是“解放数据库CPU , 将计算转移到服务层” , 并发量 大的情况下 , 这些功能很可能将数据库拖死 , 业务逻辑放到服务层具备更好的扩展性 , 能够轻易实现 “增机器就加性能”。数据库擅长存储与索引 , CPU计算还是上移吧!

  5. 禁止存储大文件或者大照片

    解读: 为何要让数据库做它不擅长的事情?大文件和照片存储在文件系统 , 数据库里存URI多好。

3.2 命名规范

  1. 只允许使用内网域名 , 而不是ip连接数据库

  2. 线上环境、开发环境、测试环境数据库内网域名遵循命名规范

    业务名称: xxx

    线上环境: xxx.db

    开发环境: xxx.rdb

    测试环境: xxx.tdb

    从库在名称后加-s标识 , 备库在名称后加-ss标识

    线上从库: xxx-s.db

    线上备库: xxx-sss.db

  3. 库名、表名、字段名: 小写 , 下划线风格 , 不超过32个字符 , 必须见名知意 , 禁止拼音英文混用

  4. 库名与应用名称尽量一致 , 表名:t_业务名称_表的作用 , 主键名:pk_xxx , 非唯一索引名:idx_xxx , 唯一键索引名:uk_xxx

3.3 表设计规范

  1. 单实例表数目必须小于500

    单表行数超过500万行或者单表容量超过2GB , 才推荐进行分库分表。

    说明: 如果预计三年后的数据量根本达不到这个级别 , 请不要在创建表时就分库分表

  2. 单表列数目必须小于30

  3. 表必须有主键 , 例如自增主键

    解读:

    • 主键递增 , 数据行写入可以提高插入性能 , 可以避免page分裂 , 减少表碎片提升空间和内存的使用

    • 主键要选择较短的数据类型 , Innodb引擎普通索引都会保存主键的值 , 较短的数据类型可以有效的 减少索引的磁盘空间 , 提高索引的缓存效率

    • 无主键的表删除 , 在row模式的主从架构 , 会导致备库夯住

  4. 禁止使用外键 , 如果有外键完整性约束 , 需要应用程序控制

    解读: 外键会导致表与表之间耦合 , updatedelete操作都会涉及相关联的表 , 十分影响sql 的性能 , 甚至会造成死锁。高并发情况下容易造成数据库性能 , 大数据高并发业务场景数据库使用以性能优先

3.4 字段设计规范

  1. 必须把字段定义为NOT NULL并且提供默认值

    解读:

    • null的列使索引/索引统计/值比较都更加复杂 , 对MySQL来说更难优化

    • null这种类型MySQL内部需要进行特殊处理 , 增加数据库处理记录的复杂性;同等条件下 , 表中有较 多空字段的时候 , 数据库的处理性能会降低很多

    • null值需要更多的存储空 , 无论是表还是索引中每行中的null的列都需要额外的空间来标识

    • null 的处理时候 , 只能采用is nullis not null , 而不能采用=in<<>!=not in这些操作符 号。如: where name!=’shenjian’ , 如果存在namenull值的记录 , 查询结果就不会包含namenull 值的记录

  2. 禁止使用TEXTBLOB类型 解读: 会浪费更多的磁盘和内存空间 , 非必要的大量的大字段查询会淘汰掉热数据 , 导致内存命中率急 剧降低 , 影响数据库性能

  3. 禁止使用小数存储货币 解读: 使用整数吧 , 小数容易导致钱对不上

  4. 必须使用varchar(20)存储手机号

    解读:

    • 涉及到区号或者国家代号 , 可能出现+-()

    • 手机号会去做数学运算么?

    • varchar可以支持模糊查询 , 例如: like“138%”

  5. 禁止使用ENUM , 可使用TINYINT代替

    解读:

    • 增加新的ENUM值要做DDL操作

    • ENUM的内部实际存储就是整数 , 你以为自己定义的是字符串?

3.5 索引设计规范

  1. 单表索引建议控制在5个以内

  2. 单索引字段数不允许超过5个 解读: 字段超过5个时 , 实际已经起不到有效过滤数据的作用了

  3. 禁止在更新十分频繁、区分度不高的属性上建立索引

    解读:

    • 更新会变更B+树 , 更新频繁的字段建立索引会大大降低数据库性能

    • “性别”这种区分度不大的属性 , 建立索引是没有什么意义的 , 不能有效过滤数据 , 性能与全表扫描类似

  4. 建立组合索引 , 必须把区分度高的字段放在前面

    解读: 能够更加有效的过滤数据

3.6 SQL使用规范

  1. 禁止使用SELECT * , 只获取必要的字段 , 需要显示说明列属性

    解读:

    • 读取不需要的列会增加CPUIONET消耗

    • 不能有效的利用覆盖索引

    • 使用SELECT *容易在增加或者删除字段后出现程序BUG

  2. 禁止使用INSERT INTO t_xxx VALUES(xxx) , 必须显示指定插入的列属性

    解读: 容易在增加或者删除字段后出现程序BUG

  3. 禁止使用属性隐式转换

    解读: SELECT uid FROM t_user WHERE phone=13812345678 会导致全表扫描 , 而不能命中phone 索引 , 猜猜为什么?(这个线上问题不止出现过一次)

  4. 禁止在WHERE条件的属性上使用函数或者表达式

    解读: SELECT uid FROM t_user WHERE from_unixtime(day)>='2017-02-15' 会导致全表扫描, 正确的写法是: SELECT uid FROM t_user WHERE day>= unix_timestamp('2017-02-15 00:00:00')

  5. 禁止负向查询 , 以及%开头的模糊查询

    解读:

    • 负向查询条件: NOT!=<>!<!>NOT INNOT LIKE等 , 会导致全表扫描

    • %开头的模糊查询 , 会导致全表扫描

  6. 禁止大表使用JOIN查询 , 禁止大表使用子查询

    解读: 会产生临时表 , 消耗较多内存与CPU , 极大影响数据库性能

  7. 禁止使用OR条件 , 必须改为IN查询

    解读: 旧版本MysqlOR查询是不能命中索引的 , 即使能命中索引 , 为何要让数据库耗费更多的CPU帮助实施查询优化呢?

  8. 应用程序必须捕获SQL异常 , 并有相应处理

熊熊