ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

数据库性能优化_SQL优化

数据库性能优化_SQL优化

调优基础

执行计划是一条 SQL 语句在数据库中的执行过程或访问路径的描述。基于 代价的优化器(CBO)产生的执行计划,对系统的查询性能至关重要。

影响性能的环境因素

CPU

CPU 决定了计算速度,一组基本参数能控制 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,达梦数据库的数据对象其实对应的是系统表上的一些信息, 内存中的数据对象是通过将系统表上的信息取出并解析出来得到的,该缓冲区一 是避免了频繁向磁盘请求获取系统表信息,二是可以减少系统表信息解析开销, 在数据对象较多(比如存在非常多分区很多的表)时建议放大。

4.SQL 缓冲区 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 bs=32k count=20k oflag=dsync

不同类型的存储,使用不同的调度算法:

  • 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 STATE='ACTIVE' -- 只查那些正在“说话”(运行中)的记录 ) 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_DMSQL=1 DMSQL(存储过程/函数) 的监控开关

操作系统命令获取数据库服务的热点访问项

perf top

nmon 和 iotop

部署了 nmon 监控工具的时候,需要查看目前服务器的性能瓶颈。 使用 iotop 命令主要分析磁盘的写等待和哪些进程占用的 IO 高。

可以用iotop -o看看是不是dmserver(达梦进程)在疯狂写日志。如果是,回头调整你之前看到的ASYNC_FLUSHBUF_SIZE参数,减少刷盘频率,降低IO压力。

分析堆栈

堆栈:当前在执行哪个函数,以及它是被谁调用的”的一张“调用轨迹图”

core文件:当程序(比如达梦数据库)突然崩溃(Segmentation Fault、异常退出等),操作系统会把程序崩溃那一瞬间的内存内容完整地保存下来,生成一个文件。

  • 程序崩溃时所有线程的堆栈信息(每个线程在做什么)

  • 所有变量的值(包括SQL语句、参数、数据等)

  • 内存中的数据页内容

  • CPU寄存器的状态

分析堆栈可以确定SQL到底卡在哪里,配置的参数是否生效,一些数据库内部机制影响(死锁等)。

可以配置操作系统保存 core 文件

有 core 文件时

ps -ef | grep dmserver

gdb -p 12345

利用 gdb 分析打印堆栈

[dmdba@localhost bin]$ gdb dmserver core.3134

//使用gdb打开core文件

开始分析

利用 dmrdc 工具扫描 core 文件

[dmdba@localhost bin]$ ./dmrdc sfile=core.3134

使用gdb查看完后,务必执行detach再退出,否则数据库进程会被一直挂起,导致服务不可用。

无 core 文件时

先找到达梦数据库进程的PID(进程ID),然后用工具去“抓住”这个进程,查看它的堆栈。

ps -ef | grep dmserver

pstack 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分组聚合操作。

括号里的三个数字代表数据库代价[16,4999,60] 估算的总代价,估算的返回行数,估算的每行字节数

如何分析执行计划

看有没有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(打包返回)

收集统计信息后,执行计划从全表扫描变成走索引了。

  1. EXPLAIN是基于统计信息的“预测”。它的准确性完全取决于统计信息的新鲜度。

  2. 索引的价值取决于数据的选择性。对于访问极少量数据的查询(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 salary>2500 -- 先筛原始员工行,工资大于2500 GROUP BY dept_id HAVING SUM(salary)>6000; -- 再筛分组后的部门

半连接通常存在与 exists/in 子句中。

  • 只返回左表数据,不取右表任何列
  • 右表只要有 1 条匹配,左表行保留;找到第一条匹配就停止对该行扫描

达梦数据库每次只做两个表的连接,如果有多个表做连接,则会先挑选两个 做连接,然后与第三个表做连接,或者与另外两个表的连接结果做连接。

创建索引一定要确保创建的索引有足够好的过滤性。

  • 联合索引(A, B, C)中,哪个列能筛掉最多数据,就放在最前面
  • 索引(A, B, C),如果B用了范围查询(><BETWEENLIKE 'abc%'),那么C列的索引就废了如果有两个范围查询,只有第一个能走索引。
  • 聚集索引:数据和索引在一起,找到索引就拿到了数据(不需要回表)。二级索引:索引只存键值,占用的IO小,找到索引后还要去聚集索引再查一次(回表)
  • PK_WITH_CLUSTER=0表示:主键索引不存储完整行数据,数据和主键分开存储,适合频繁更新主键值的场景。
  • 传统B树索引适合"区分度高"的列(如用户ID)位图索引适合"区分度低"的列(如性别、状态),用比特位存储,查询快,更新慢。
  • 让查询条件的类型和字段类型一模一样,如果查询时类型不匹配,数据库会自动转换,导致索引失效。
  • 函数索引必须是确定性函数,且不在频繁变化的表上建

  • 让索引列保持"裸列",所有转换函数都加到常量那边去。
让索引能用①等值放前,②范围放后,⑥类型一致,⑧列不动只动常量
让索引高效③选对类型,④主键少改,⑤选对场景,⑦函数稳定

达梦社区地址:https://eco.dameng.com

返回列表