Mysql--调优
1 SQL以及索引的优化
根据需求写出结构良好的SQL,然后根据SQL在表中建立合适的索引,一般在主键列或经常用于查询条件的列建立索引
打开慢查询日志定位到具体的出问题的SQL,然后使用explain、profile等工具来逐步调优,最后经过测试达到效果后上线
注:如果索引太多,或在频繁修改的表上建立索引反而会增大数据库的开销,降低数据库的性能
2 数据表结构
数据库三范式:
第一范式 数据表中每个字段都必须是不可拆分的最小单元,也就是确保每一列的原子性
第二范式 在满足第一范式的基础上,表中每一条记录必须可以被唯一地区分
第三范式 在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)
注:一个完全范式化设计的数据库经常会面临“查询缓慢”这个问题,数据库越范式化,就需要Join越多的表
可以根据场景合理地部分反规范化:
字段过多时分割表
保留部分冗余字段,当两个或多个表在查询中经常需要连接时,可以在其中一个表上增加若干冗余的字段,以避免表之间的连接过于频繁,一般在冗余列的数据不经常变动的情况下使用
增加派生列,派生列是由表中的其它多个列的计算所得,增加派生列可以减少统计运算,在数据汇总时可以大大缩短运算时间
字段类型选择:
尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED
VARCHAR的长度只分配真正需要的空间
使用枚举或整数代替字符串类型
尽量使用TIMESTAMP而非DATETIME
避免使用NULL字段,很难查询优化且占用额外索引空间
约束:
设置合理约束(非空、唯一、自增、默认、主键、外键)
3 系统配置
服务状态查看:
show status; | 查看服务器状态 |
show status like 'Connections'; | 查询连接mysql服务器次数 |
show status like 'Uptime'; | 查询当前MySQL本次启动后的运行统计时间 |
show status like 'slow_queries' | 查询慢查询次数 |
show global status like 'com_select'; | 查询自当前MySQL启动后所有连接执行的SELECT语句总数 |
show status like 'Thread_%'; | 查看MySQL服务器的线程信息 |
show [GLOBAL] status like 'com_insert'; | 查看insert语句的执行数 |
show status like 'com_select'; | 查询本次MySQL启动后执行的SELECT语句的次数 |
show variables; | 查看变量(设置) |
show warnings; | 查看最近一个sql语句产生的错误警告,看其他的需要看.err日志 |
show [full] processlist; | 显示服务器中正在运行的连接进程 |
show errors; | 显示错误 |
参数配置优化(my.cnf):
max_connections = 2000 #MySQL允许最大的进程连接数,如果经常出现Too Many Connections的错误提示,则需要增大此值
max_connect_errors = 6000 #设置每个主机的连接请求异常中断的最大次数,当超过该次数,MYSQL服务器将禁止host的连接请求,直到mysql服务器重启或通过flush hosts命令清空此host的相关信息
table_open_cache = 2048 #指示表调整缓冲区大小,table_open_cache 参数设置表高速缓存的数目
#每个连接进来,都会至少打开一个表缓存。因此, table_open_cache 的大小应与 max_connections 的设置有关
#当 Mysql 访问一个表时,如果该表在缓存中已经被打开,则可以直接访问缓存
#如果还没有被缓存,但是在 Mysql 表缓冲区中还有空间,那么这个表就被打开并放入表缓冲区
#如果表缓存满了,则会按照一定的规则将当前未用的表释放,或者临时扩大表缓存来存放
#使用表缓存的好处是可以更快速地访问表中的内容,执行 flush tables 会清空缓存的内容
#一般来说,可以通过查看数据库运行峰值时间的状态值 Open_tables 和 Opened_tables ,判断是否需要增加 table_open_cache 的值
#其中 open_tables 是当前打开的表的数量, Opened_tables 则是已经打开的表的数量
#即如果open_tables接近table_open_cache的时候,并且Opened_tables这个值在逐步增加,那就要考虑增加这个值的大小了
#当Table_locks_waited比较高的时候,也需要增加table_open_cache
#注:不能盲目地把table_open_cache设置成很大的值,设置太大超过了shell的文件描述符(通过ulimit -n查看),造成文件描述符不足,从而造成性能不稳定或者连接失败
#较为合适的值:Open_tables / Opened_tables >= 0.85 Open_tables / table_open_cache <= 0.95
external-locking = FALSE #使用–skip-external-locking MySQL选项以避免外部锁定,该选项默认开启
max_allowed_packet = 32M #设置在网络传输中一次消息传输量的最大值,系统默认值 为1MB,最大值是1GB,必须设置1024的倍数
sort_buffer_size = 1M #Sort_Buffer_Size 是一个connection级参数,在每个connection(session)第一次需要使用这个buffer的时候,一次性分配设置的内存
#Sort_Buffer_Size 并不是越大越好,由于是connection级的参数,过大的设置+高并发可能会耗尽系统内存资源
#在Linux上,有256KB和2MB的阈值,较大的值可能会显著降低内存分配
join_buffer_size = 1M #用于表间关联缓存的大小,和sort_buffer_size一样,该参数对应的分配内存也是每个连接独享
#如果join语句不多,不用调整此参数, 如果join语句较多的话, 可将此参数调整到1M, 如果内存充足,还可增大到2M
thread_cache_size = 300 #服务器线程缓存,这个值表示可以重新利用保存在缓存中线程的数量
#当断开连接时如果缓存中还有空间,那么客户端的线程将被放到缓存中,如果线程重新被请求,那么请求将从缓存中读取
#如果缓存中是空的或者是新的请求,那么这个线程将被重新创建,如果有很多新的线程,增加这个值可以改善系统性能
#通过比较 Connections 和 Threads_created 状态的变量,可以看到这个变量的作用
#设置规则如下:1GB 内存配置为8,2GB配置为16,3GB配置为32,4GB或更高内存,可配置更大
query_cache_size = 64M #一个SELECT查询在DB中工作后,DB会把该语句缓存下来,当同样的一个SQL再次来到DB里调用时,DB在该表没发生变化的情况下把结果从缓存中返回给Client
#注:DB在利用Query_cache工作时,要求该语句涉及的表在这段时间内没有发生变更
# 如果有变化,首先要把Query_cache和该表相关的语句全部置为失效,然后再写入更新
# 如果Query_cache非常大,该表的查询结构又比较多,查询语句失效也慢,一个更新或是Insert就会很慢,这样看到的就是Update或是Insert怎么这么慢了
#所以在数据库写入量或是更新量也比较大的系统,该参数不适合分配过大,而且在高并发,写入量大的系统,建议把该功能禁掉
query_cache_limit = 4M #指定单个查询能够使用的缓冲区大小,缺省为1M
query_cache_min_res_unit = 2k #默认是4KB,设置值大对大数据查询有好处,但如果查询都是小数据查询,就容易造成内存碎片和浪费
#查询缓存碎片率 = Qcache_free_blocks / Qcache_total_blocks * 100%
#如果查询缓存碎片率超过20%,可以用FLUSH QUERY CACHE整理缓存碎片,或者试试减小query_cache_min_res_unit,如果查询都是小数据量的话
#查询缓存利用率 = (query_cache_size – Qcache_free_memory) / query_cache_size * 100%
#查询缓存利用率在25%以下的话说明query_cache_size设置的过大,可适当减小
#查询缓存利用率在80%以上而且Qcache_lowmem_prunes > 50的话说明query_cache_size可能有点小,要不就是碎片太多
#查询缓存命中率 = (Qcache_hits – Qcache_inserts) / Qcache_hits * 100%
default-storage-engine = MyISAM #修改默认存储引擎(默认为default_table_type = InnoDB)
thread_stack = 192K #设置MYSQL每个线程的堆栈大小,可设置范围为128K至4GB,默认为192KB
tmp_table_size = 256M #默认32M,如果一张临时表超出该大小,MySQL产生一个 The table tbl_name is full 形式的错误
#如果做很多高级 GROUP BY 查询,增加 tmp_table_size 值,如果超过该值,则会将临时表写入磁盘
key_buffer_size = 2048M #批定用于索引的缓冲区大小,增加它可以得到更好的索引处理性能,对于内存在4GB左右的服务器来说,该参数可设置为256MB或384MB
read_buffer_size = 1M #MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区,read_buffer_size变量控制这一缓冲区的大小
#如果对表的顺序扫描请求非常频繁,并且频繁扫描进行得太慢,可以通过增加该变量值以及内存缓冲区大小提高其性能
#和sort_buffer_size一样,该参数对应的分配内存也是每个连接独享
read_rnd_buffer_size = 16M #MySql的随机读(查询操作)缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区
#进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值
#但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大
bulk_insert_buffer_size = 64M #批量插入数据缓存大小,可以有效提高插入效率,默认为8M
innodb_additional_mem_pool_size = 16M #设置 InnoDB 存储的数据目录信息和其它内部数据结构的内存池大小,类似于Oracle的library cache
innodb_buffer_pool_size = 2048M #Innodb相比MyISAM表对缓冲更为敏感
#MyISAM可以在默认的 key_buffer_size 设置下可以正常运行,然而Innodb在默认的 innodb_buffer_pool_size 设置下却运行非常缓慢
#由于Innodb把数据和索引都缓存起来,无需留给操作系统太多的内存,因此如果只需要用Innodb的话则可以设置它高达 70-80% 的可用内存
#一些应用于 key_buffer 的规则有 — 如果你的数据量不大,并且不会暴增,那么无需把 innodb_buffer_pool_size 设置的太大
innodb_data_file_path = ibdata1:1024M:autoextend #表空间文件 重要数据, 可以设置多个
innodb_file_io_threads = 4 #文件IO的线程数,一般为 4,但是在 Windows 下,可以设置得较大
innodb_thread_concurrency = 8 #服务器有几个CPU就设置为几
innodb_flush_log_at_trx_commit = 2 #如果将此参数设置为1,将在每次提交事务后将日志写入磁盘,为提供性能,可以设置为0或2,但要承担在发生故障时丢失数据的风险
#0表示事务日志写入日志文件,而日志文件每秒刷新到磁盘一次
#2表示事务日志将在提交时写入日志,但日志文件每次刷新到磁盘一次
innodb_log_buffer_size = 16M #日志文件所用的内存大小,以M为单位,缓冲区更大能提高性能,但意外的故障将会丢失数据.MySQL开发人员建议设置为1-8M之间
innodb_log_file_size = 128M #数据日志文件的大小,以M为单位,更大的设置可以提高性能,但也会增加恢复故障数据库所需的时间
innodb_log_files_in_group = 3 #为提高性能,MySQL可以以循环方式将日志文件写到多个文件。推荐设置为3M
innodb_max_dirty_pages_pct = 90 #Buffer_Pool中Dirty_Page所占的数量,直接影响InnoDB的关闭时间
#此参数可以直接控制Dirty_Page在Buffer_Pool中所占的比率,而且innodb_max_dirty_pages_pct是可以动态改变的
#所以,在关闭InnoDB之前先将innodb_max_dirty_pages_pct调小,强制数据块Flush一段时间,则能够大大缩短 MySQL关闭的时间
innodb_lock_wait_timeout = 120 #InnoDB 有其内置的死锁检测机制,能导致未完成的事务回滚
#但是,如果结合InnoDB使用MyISAM的lock tables 语句或第三方事务引擎,则InnoDB无法识别死锁
#为消除这种可能性,可以将此参数设置为一个整数值,指示 MySQL在允许其他事务修改那些最终受事务回滚的数据之前要等待多长时间(秒数)
innodb_file_per_table = 0 #独享表空间(关闭)
wait_timeout = 10 #指定一个请求的最大连接时间,对于4GB左右的内存服务器来说,可以将其设置为5-10
4 缓存
缓存作用:
减轻数据库的压力,减少访问时间
使用场景:
短时间内相同数据重复查询多次且数据更新不频繁,这个时候可以选择先从缓存查询,查询不到再从数据库加载并回设到缓存的方式
高并发查询热点数据,后端数据库不堪重负,可以用缓存来扛
缓存选择:
如果数据量小,并且不会频繁地增长又清空(这会导致频繁地垃圾回收),那么可以选择本地缓存
其他情况,可以考虑缓存服务,如Redis/Tair/Memcache等
缓存穿透:
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找
如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力
可以对查询结果为空的情况也进行缓存,缓存时间设置短点,或者该key对应的数据insert了之后清理缓存
缓存并发:
有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况
如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题
可以对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询
缓存雪崩(失效):
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统造成很大压力
对于不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
防止缓存空间不够用:
给缓存服务,选择合适的缓存逐出算法,比如最常见的LRU
针对当前设置的容量,设置适当的警戒值,比如10G的缓存,当缓存数据达到8G的时候,就开始发出报警,提前排查问题或者扩容
给一些没有必要长期保存的key,尽量设置过期时间