数据库性能优化_SQL优化 调优基础执行计划是一条 SQL 语句在数据库中的执行过程或访问路径的描述。基于 代价的优化器(CBO)产生的执行计划对系统的查询性能至关重要。影响性能的环境因素CPUCPU 决定了计算速度一组基本参数能控制 CBO 的代价计算行为并影响 着最终的结果。这些参数可以在 dm.ini 中设置但是通常不需要轻易修改。V$DM_INI是与dm.ini文件对应的视图。为了表示 CPU 代价CBO 假定一些“标准”的数据库操作占用了一定数量 的 CPU 时钟周期因此 CPU 的工作频率决定了执行“标准”操作的时间。CPU 的缺省速度工作在 3Ghz用参数 CPU_SPEED 来表示。这个参数的意义是每 1ms CPU 的时钟周期数目注意单位是毫秒。每个计划的操作符都是一个三元组。1第一个数字代表的是该操作需要的代价2第二个数字代表估算该操作输出的行数3第三个数字表示每行记录的字节数。内存达梦数据库使用的内存可以分为三部分缓冲区、内存池、其他内存区。缓冲区1.数据缓冲区 从磁盘中读取的数据页在内存中的镜像dm.ini 中的 BUFFER、FAST_POOL、 RECYCLE、KEEP 等普通数据页使用 LRU 算法淘汰。每条 SQL 语句请求的数据都是从数据缓冲区中取得的若不存在才会从磁盘中读取数据并加载到 缓冲区中。2日志缓冲区 日志缓冲区对应 ini 参数中的 RLOG_BUF_SIZE数据库日志将对磁盘的随 机写转换成顺序写。3字典缓冲区 字 典 缓 冲 区 是 保 存 数 据 库 对 象 的 一 片 缓 冲 区 对 应 INI 参 数 DICT_CACHE_SIZE达梦数据库的数据对象其实对应的是系统表上的一些信息 内存中的数据对象是通过将系统表上的信息取出并解析出来得到的该缓冲区一 是避免了频繁向磁盘请求获取系统表信息二是可以减少系统表信息解析开销 在数据对象较多比如存在非常多分区很多的表时建议放大。4SQL 缓冲区 SQL CACHE POOL简称 SCP对应 INI 参数 CACHE_POOL_SIZE是用 来存储包信息PACKAGE、执行计划、结果集缓存的一片专用缓存区域对 于 SQL 类别比较多或者 PKG 比较多、复杂的系统建议将该参数调大。内存池服务器启动时首先会从操作系统申请一大片内存后续服务器在运行过程中 一般情况下很多需要内存分配的地方都是从该池分配如果需要的内存大于配 置值(MEM_POOL)该池也会自动扩展一般情况下不收缩最大扩展到 MAX_OS_MEMORY 大小。其他运行内存池服务器运行过程中内存的使用有两种模式一种是直接从内存池申请需要 的内存大小另外一种方式是从操作系统申请一大片内存来做成自己模块的内存 池来使用VM_POOL、SESS_POOL、RT_HEAP 等等这样可以减少频繁从 数据库主内存池申请内存的开销一般来说一个会话可以理解为一个单独的运行环境有自己的私有内存池。如何确认内存泄漏再通过 TOP 命令查看数据库进程的 res 和 virt 值二者相差较大则为内存泄漏。磁盘磁盘的读写速度决定了入库的性能。服 务器 IO 的最小操作单元是块block size。测试磁盘读写速度dd if/dev/zero of/dbdata/dmdata/test bs32k count20k oflagdsync不同类型的存储使用不同的调度算法SSD 固态硬盘NOOP 调度SAS 机械盘DEADLINE 调度Raid 阵列RAID0统计信息统计信息是数据库收集的表、列、索引的数据分布元数据。优化器 CBO基于代价的优化器依靠统计信息计算不同执行计划的代价选出最优 SQL 执行计划。代价优化器依赖统计信息来评估选择率。所谓选择率是指一个数据集被应 用一个条件谓词后符合条件的记录数与原总记录数的比例。如果没有统计信息 按照下列原则来确定选择率。性能优化相关的 INI参数性能问题定位进行 SQL 查询时通常希望查询越快越好所以代价(COST)以时间单位来定义。优化器CBO在分析的过程中为每一个可选的计划计算其执行代价并保留一个最优的计划。 计算出一个与实际执行相接近的代价值是一件困难的事影响实际执行代价的因素非常多。定位负载在日常运维/性能测试的时候常常会遇到数据库慢的问题通过top 命令查看 cpu 使用率如果一台数据库服务器的 CPU 使用率高 那么记住一个准则所有导致 CPU 使用率高的原因都是因为 SQL执行慢。系统视图查看系统视图查询当前正在执行的会话信息。找出当前所有正在运行ACTIVE并且已经执行了超过1秒的SQL语句并显示出它们的完整内容。SELECT * FROM ( -- 这里是一个子查询内层查询 SELECT SESS_ID, -- 会话的ID就像“通话记录编号” SQL_TEXT, -- 当前正在执行的SQL的“摘要”可能不完整 DATEDIFF(SS, LAST_SEND_TIME, SYSDATE) AS SS, -- 计算“发呆”秒数 SF_GET_SESSION_SQL(SESS_ID) AS FULLSQL -- 通过ID获取“完整”的SQL语句 FROM V$SESSION -- 从这个“通话记录本”里查 WHERE STATEACTIVE -- 只查那些正在“说话”运行中的记录 ) WHERE SS 1; -- 找出那些已经“发呆”超过1秒的“说话”记录V$SESSION是动态性能V$SQL查看SQL语句的具体信息执行次数、消耗时间等。V$LOCK查看锁信息用来排查“死锁”问题。V$PROCESS查看数据库的后台进程。DBA_TABLES/USER_TABLES查看所有表或当前用户下的表的元数据比如表结构、大小等。SQL_TEXT 列记录的是部分 SQL 语句FULLSQL 列存储了完整的执行 SQL 语句。日志分析分析 dmsql_log 日志来获取 SQL 语句需要掌握 Dmlog_DM7_X.jar 使 用方法。需要安装 jdk 环境。需要配置异步日志刷新避免记录 SQL。需要配置cat sqllog.ini文件将ASYNC_FLUSH参数设为1.刷盘就是把内存中的数据写到磁盘硬盘上。数据库运行过程中SQL日志会先存在内存缓冲区里内存速度快然后再写到磁盘文件里磁盘速度慢但数据会永久保存。同步刷盘ASYNC_FLUSH 0数据库产生一条SQL日志 → 写进内存缓冲区立刻把这个缓冲区的内容写到磁盘文件上必须等磁盘写完了数据已安全落地才返回结果给客户端继续处理下一条SQL。异步刷盘ASYNC_FLUSH 1数据库产生一条SQL日志 → 快速写进内存缓冲区瞬间完成不等磁盘写完直接返回结果给客户端继续处理下一条SQL。后台有一个专门的“刷盘线程”在后台悄悄地把内存里的日志往磁盘上写。可能每秒批量写一次也可能等缓冲区满了再写。性能监控ENABLE_MONITOR 1 监控功能的一级总开关MONITOR_SQL_EXEC 1 SQL执行监控在调优的会话级窗口MONITOR_TIME 1 时间监控ENABLE_MONITOR_DMSQL1 DMSQL存储过程/函数 的监控开关操作系统命令获取数据库服务的热点访问项perf topnmon 和 iotop部署了 nmon 监控工具的时候需要查看目前服务器的性能瓶颈。 使用 iotop 命令主要分析磁盘的写等待和哪些进程占用的 IO 高。可以用iotop -o看看是不是dmserver达梦进程在疯狂写日志。如果是回头调整你之前看到的ASYNC_FLUSH和BUF_SIZE参数减少刷盘频率降低IO压力。分析堆栈堆栈当前在执行哪个函数以及它是被谁调用的”的一张“调用轨迹图”。core文件当程序比如达梦数据库突然崩溃Segmentation Fault、异常退出等操作系统会把程序崩溃那一瞬间的内存内容完整地保存下来生成一个文件。程序崩溃时所有线程的堆栈信息每个线程在做什么所有变量的值包括SQL语句、参数、数据等内存中的数据页内容CPU寄存器的状态分析堆栈可以确定SQL到底卡在哪里配置的参数是否生效一些数据库内部机制影响死锁等。可以配置操作系统保存 core 文件有 core 文件时ps -ef | grep dmservergdb -p 12345利用 gdb 分析打印堆栈[dmdbalocalhost bin]$ gdb dmserver core.3134//使用gdb打开core文件开始分析利用 dmrdc 工具扫描 core 文件[dmdbalocalhost bin]$ ./dmrdc sfilecore.3134使用gdb查看完后务必执行detach再退出否则数据库进程会被一直挂起导致服务不可用。无 core 文件时先找到达梦数据库进程的PID进程ID然后用工具去“抓住”这个进程查看它的堆栈。ps -ef | grep dmserverpstack 12345 /tmp/stack_20260810.log尽量使用 gdb 来实现这样可以避免由于 pstack 可能导致 dmserver 变为僵尸进 程处理僵尸进程需要使用命令 ps -ef|grep defunct 找到该进程的父进程并 kill 掉。 若无法 kill 释放则只能进行重启机器。trace事件遇到特定问题比如一条SQL跑得特别慢可以在当前的数据库会话里打开特定的事件让数据库开始记录执行过程中的详细内部信息。这些信息会被写入到服务器的trace日志文件中可以提供分析。如何查看执行计划SQL语句前加上EXPLAIN关键字操作符中文名通俗解释表访问类CSCN聚集索引全扫描全表扫描。从头到尾把整张表读一遍数据量大时性能较差。SSEK二级索引范围扫描走索引。通过索引快速定位到满足条件的行性能较好。CSEK聚集索引范围扫描走主键索引。和SSEK类似但效率更高不需要回表。BLKUP回表二次扫描。先用二级索引找到数据的位置再根据这个位置去表里把完整的数据行取出来。表连接类NEST LOOP嵌套循环连接驱动表每取一行就去被驱动表里匹配一次。适合小表驱动大表且连接条件能走索引的情况。HASH JOIN哈希连接对一张表建哈希表另一张表去探测。适合大表等值连接且连接列有索引或数据量大时效率高。结果处理类NSET结果集计划的最顶层表示将最终结果返回给客户端。PRJT投影从结果中挑出你SELECT的列。SLCT选择/过滤执行WHERE条件过滤数据。HAGR哈希分组聚集执行GROUP BY分组聚合操作。括号里的三个数字代表数据库代价[16499960] 估算的总代价估算的返回行数估算的每行字节数如何分析执行计划看有没有CSCN全表扫描 如果有且表数据量大 → ⚠️ 风险信号看有没有BLKUP回表 回表次数多 → 考虑用覆盖索引优化看表连接方式是NEST LOOP还是HASH JOIN大表连接用HASH JOIN更优看每个操作符的rows预估行数 预估行数严重偏离实际 → 统计信息过期执行计划实践分析创建部门表和员工表插入部门数据插入10万条员工数据。CREATE TABLE departments ( dept_id INT PRIMARY KEY, dept_name VARCHAR(50) NOT NULL, location VARCHAR(100) ); CREATE TABLE employees ( emp_id INT PRIMARY KEY, emp_name VARCHAR(50) NOT NULL, age INT, dept_id INT, salary DECIMAL(10,2), hire_date DATE ); INSERT INTO departments VALUES (1, 技术部, 北京); INSERT INTO departments VALUES (2, 市场部, 上海); INSERT INTO departments VALUES (3, 财务部, 深圳); INSERT INTO departments VALUES (4, 人事部, 广州); INSERT INTO departments VALUES (5, 研发部, 杭州); INSERT INTO employees (emp_id, emp_name, age, dept_id, salary, hire_date) SELECT LEVEL AS emp_id, 员工 || LEVEL AS emp_name, TRUNC(DBMS_RANDOM.VALUE(20, 60)) AS age, TRUNC(DBMS_RANDOM.VALUE(1, 6)) AS dept_id, ROUND(DBMS_RANDOM.VALUE(5000, 30000), 2) AS salary, DATE 2020-01-01 TRUNC(DBMS_RANDOM.VALUE(0, 1500)) AS hire_date FROM DUAL CONNECT BY LEVEL 100000;执行看没有索引时的全表扫描EXPLAIN SELECT * FROM employees WHERE salary 20000;建立索引并查看执行计划建了索引还是全表扫描可能是表里salary 20000的数据优化器预估有5000 条。在 10 万条总数据中这占了5%。回表代价高索引只存了salary和主键emp_id。但你要的是SELECT *这意味着索引每找到一个符合条件的emp_id就得拿着它去数据表里“回表”取出完整的一整行数据。5000 次“回表”在优化器看来是一笔不小的开销。这个计划的执行流程是CSCN2(全表扫) →SLCT2(过滤行执行WHERE条件的地方) →PRJT2(投影列) →NSET2(返回结果)搜集统计信息后再次查询执行顺序从下往上、从内到外SSEK2索引里找位置→BLKUP2按位置取数据→PRJT2选需要的列→NSET2打包返回收集统计信息后执行计划从全表扫描变成走索引了。EXPLAIN是基于统计信息的“预测”。它的准确性完全取决于统计信息的新鲜度。索引的价值取决于数据的选择性。对于访问极少量数据的查询SSEK索引是神器对于访问大量数据的查询全表扫描CSCN可能更优。对于需要返回大量行的查询当需要返回表中将近20%的数据时走索引反而更慢全表扫描通常更高效因为顺序I/O比大量随机I/O快得多。走索引的回表操作是随机IO。更新统计信息后执行计划依然走全表扫描但代价和记录行数会更加准确。强制走索引看执行计划代价比全表扫描要高。建索引、加字段、删字段后 →立即收集统计信息批量导入/删除大量数据后超过总行数10%→立即收集统计信息定期如每天或每周→对核心表收集统计信息表连接的执行计划扫描两张表然后对两张表进行哈希内连接投影后得到结果集。SQL 优化SQL语法顺序SELECT[DISTINCT]---FROM---WHERE---GROUP BY---HAVING---UNION---ORDER BY选、表、筛、组、组筛、并、排序SQL执行顺序FROM---WHERE---GROUP BY---HAVING---SELECT---DISTINCT---UNION---ORDER B跑表→筛→分组→组筛→选→去重→合并→排序from找表where筛选符合条件的行GROUP BY把 WHERE 过滤之后的数据按照指定字段分组HAVING执行分组后过滤分组后筛分组结果可以用聚合函数SUM、COUNT、AVG SELECT挑选需要输出的列等DISTINCT对数据去重UNION将前后两个查询的结果集合并自动去重ORDER BY 对最终结果集排序。注意WHERE不能写SUM聚合SELECT dept_id, SUM(salary) total_sal FROM emp WHERE salary2500 -- 先筛原始员工行工资大于2500 GROUP BY dept_id HAVING SUM(salary)6000; -- 再筛分组后的部门半连接通常存在与 exists/in 子句中。只返回左表数据不取右表任何列右表只要有 1 条匹配左表行保留找到第一条匹配就停止对该行扫描达梦数据库每次只做两个表的连接如果有多个表做连接则会先挑选两个 做连接然后与第三个表做连接或者与另外两个表的连接结果做连接。创建索引一定要确保创建的索引有足够好的过滤性。联合索引(A, B, C)中哪个列能筛掉最多数据就放在最前面。索引(A, B, C)如果B用了范围查询、、BETWEEN、LIKE abc%那么C列的索引就废了如果有两个范围查询只有第一个能走索引。聚集索引数据和索引在一起找到索引就拿到了数据不需要回表。二级索引索引只存键值占用的IO小找到索引后还要去聚集索引再查一次回表PK_WITH_CLUSTER0表示主键索引不存储完整行数据数据和主键分开存储适合频繁更新主键值的场景。传统B树索引适合区分度高的列如用户ID位图索引适合区分度低的列如性别、状态用比特位存储查询快更新慢。让查询条件的类型和字段类型一模一样如果查询时类型不匹配数据库会自动转换导致索引失效。函数索引必须是确定性函数且不在频繁变化的表上建让索引列保持裸列所有转换函数都加到常量那边去。让索引能用①等值放前②范围放后⑥类型一致⑧列不动只动常量让索引高效③选对类型④主键少改⑤选对场景⑦函数稳定达梦社区地址https://eco.dameng.com