• [技术干货] Libcomm 通信库
    GaussDB(DWS)为解决建联过多的问题,实现了 Libcomm 通信库(即逻辑连接通信库),在一个物理长连接上模拟n个逻辑连接,使得所有并发的数据跑在一个物理连接上。比如DN1需要给DN2发送数据,并发数1000,在原有逻辑下,DN1需要建立与DN2连接的1000个线程与之进行交互,消耗了大量的连接、内存、fd资源,而改造Libcomm通信库之后,DN1 与 DN2 仅需建立一个真正的物理连接,在这个物理连接上可以建立很多个逻辑链接,这样可以使得1000个并发就可以用同一个物理连接进行数据交互。 那么GaussDB(DWS)的逻辑连接是怎么实现的呢?首先我们从连接数据流入手,挖掘其实现逻辑。物理连接支持TCP、RDMA等协议连接,以TCP为例,其物理连接数据流可以分为两部分,即数据包头+数据。数据包头为固定长度,其中包含逻辑连接号和数据块长度,用来区别逻辑连接,并接收每个连接各自对应的数据。send queue:producer 发送线程将要发送的数据先push到一个无锁队列中,push完成之后,producer线程就可以继续做自己的事情了; send proxy thread:通信存在一个发送端代理线程,会统一将无锁队列中的数据,通过物理连接发送到对端。
  • [问题求助] 求助,求助帖: 云服务实例gaussdb集中式, 建表是不是不能指定列存以及不能创建分布式键
    求助,gaussdb集中式是不是不能创建 分布式键的表吗?CREATE TABLE tb_t3(c1 int,c2 int) DISTRIBUTE BY RANGE(c1)(    SLICE s1 VALUES LESS THAN (100),    SLICE s2 VALUES LESS THAN (200),    SLICE s3 VALUES LESS THAN (MAXVALUE) );
  • [技术干货] 健康体检项目之运营数据统计
    Apache POI 是用Java编写的免费开源的跨平台的Java API,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能,其中使用最多的就是使用POI操作Excel文件。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。一、运营数据统计1.1 需求分析通过运营数据统计可以展示出体检机构的运营情况,包括会员数据、预约到诊数据、热门套餐等信息。本章节就是要通过一个表格的形式来展示这些运营数据。效果如下图:  1.2 完善页面运营数据统计对应的页面为/pages/report_business.html。1.2.1 定义模型数据定义数据模型,通过VUE的数据绑定展示数据<script> var vue = new Vue({ el: '#app', data:{ reportData:{ reportDate:null, todayNewMember :0, totalMember :0, thisWeekNewMember :0, thisMonthNewMember :0, todayOrderNumber :0, todayVisitsNumber :0, thisWeekOrderNumber :0, thisWeekVisitsNumber :0, thisMonthOrderNumber :0, thisMonthVisitsNumber :0, hotSetmeal :[] } } }) </script><div class="box" style="height: 900px"> <div class="excelTitle" > <el-button @click="exportExcel">导出Excel</el-button>运营数据统计 </div> <div class="excelTime">日期:{{reportData.reportDate}}</div> <table class="exceTable" cellspacing="0" cellpadding="0"> <tr> <td colspan="4" class="headBody">会员数据统计</td> </tr> <tr> <td width='20%' class="tabletrBg">新增会员数</td> <td width='30%'>{{reportData.todayNewMember}}</td> <td width='20%' class="tabletrBg">总会员数</td> <td width='30%'>{{reportData.totalMember}}</td> </tr> <tr> <td class="tabletrBg">本周新增会员数</td> <td>{{reportData.thisWeekNewMember}}</td> <td class="tabletrBg">本月新增会员数</td> <td>{{reportData.thisMonthNewMember}}</td> </tr> <tr> <td colspan="4" class="headBody">预约到诊数据统计</td> </tr> <tr> <td class="tabletrBg">今日预约数</td> <td>{{reportData.todayOrderNumber}}</td> <td class="tabletrBg">今日到诊数</td> <td>{{reportData.todayVisitsNumber}}</td> </tr> <tr> <td class="tabletrBg">本周预约数</td> <td>{{reportData.thisWeekOrderNumber}}</td> <td class="tabletrBg">本周到诊数</td> <td>{{reportData.thisWeekVisitsNumber}}</td> </tr> <tr> <td class="tabletrBg">本月预约数</td> <td>{{reportData.thisMonthOrderNumber}}</td> <td class="tabletrBg">本月到诊数</td> <td>{{reportData.thisMonthVisitsNumber}}</td> </tr> <tr> <td colspan="4" class="headBody">热门套餐</td> </tr> <tr class="tabletrBg textCenter"> <td>套餐名称</td> <td>预约数量</td> <td>占比</td> <td>备注</td> </tr> <tr v-for="s in reportData.hotSetmeal"> <td>{{s.name}}</td> <td>{{s.setmeal_count}}</td> <td>{{s.proportion}}</td> <td></td> </tr> </table> </div>1.2.2 发送请求获取动态数据在VUE的钩子函数中发送ajax请求获取动态数据,通过VUE的数据绑定将数据展示到页面<script> var vue = new Vue({ el: '#app', data:{ reportData:{ reportDate:null, todayNewMember :0, totalMember :0, thisWeekNewMember :0, thisMonthNewMember :0, todayOrderNumber :0, todayVisitsNumber :0, thisWeekOrderNumber :0, thisWeekVisitsNumber :0, thisMonthOrderNumber :0, thisMonthVisitsNumber :0, hotSetmeal :[] } }, created() { //发送ajax请求获取动态数据 axios.get("/report/getBusinessReportData.do").then((res)=>{ this.reportData = res.data.data; }); } }) </script>根据页面对数据格式的要求,我们发送ajax请求,服务端需要返回如下格式的数据:{ "data":{ "todayVisitsNumber":0, "reportDate":"2019-04-25", "todayNewMember":0, "thisWeekVisitsNumber":0, "thisMonthNewMember":2, "thisWeekNewMember":0, "totalMember":10, "thisMonthOrderNumber":2, "thisMonthVisitsNumber":0, "todayOrderNumber":0, "thisWeekOrderNumber":0, "hotSetmeal":[ {"proportion":0.4545,"name":"粉红珍爱(女)升级TM12项筛查体检套餐","setmeal_count":5}, {"proportion":0.1818,"name":"阳光爸妈升级肿瘤12项筛查体检套餐","setmeal_count":2}, {"proportion":0.1818,"name":"珍爱高端升级肿瘤12项筛查","setmeal_count":2}, {"proportion":0.0909,"name":"孕前检查套餐","setmeal_count":1} ], }, "flag":true, "message":"获取运营统计数据成功" }1.3 后台代码1.3.1 Controller在ReportController中提供getBusinessReportData方法@Reference private ReportService reportService; ​ /** * 获取运营统计数据 * @return */ @RequestMapping("/getBusinessReportData") public Result getBusinessReportData(){ try { Map<String, Object> result = reportService.getBusinessReport(); return new Result(true,MessageConstant.GET_BUSINESS_REPORT_SUCCESS,result); } catch (Exception e) { e.printStackTrace(); return new Result(true,MessageConstant.GET_BUSINESS_REPORT_FAIL); } }1.3.2 服务接口在health_interface工程中创建ReportService服务接口并声明getBusinessReport方法package com.yunhe.service; import java.util.Map; public interface ReportService { /** * 获得运营统计数据 * Map数据格式: * todayNewMember -> number * totalMember -> number * thisWeekNewMember -> number * thisMonthNewMember -> number * todayOrderNumber -> number * todayVisitsNumber -> number * thisWeekOrderNumber -> number * thisWeekVisitsNumber -> number * thisMonthOrderNumber -> number * thisMonthVisitsNumber -> number * hotSetmeals -> List<Setmeal> */ public Map<String,Object> getBusinessReport() throws Exception; }1.3.3 服务实现类在health_service_provider工程中创建服务实现类ReportServiceImpl并实现ReportService接口package com.yunhe.service; import com.alibaba.dubbo.config.annotation.Service; import com.yunhe.dao.MemberDao; import com.yunhe.dao.OrderDao; import com.yunhe.utils.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 统计报表服务 */ @Service(interfaceClass = ReportService.class) @Transactional public class ReportServiceImpl implements ReportService { @Autowired private MemberDao memberDao; @Autowired private OrderDao orderDao; /** * 获得运营统计数据 * Map数据格式: * todayNewMember -> number * totalMember -> number * thisWeekNewMember -> number * thisMonthNewMember -> number * todayOrderNumber -> number * todayVisitsNumber -> number * thisWeekOrderNumber -> number * thisWeekVisitsNumber -> number * thisMonthOrderNumber -> number * thisMonthVisitsNumber -> number * hotSetmeal -> List<Setmeal> */ public Map<String, Object> getBusinessReport() throws Exception{ //获得当前日期 String today = DateUtils.parseDate2String(DateUtils.getToday()); //获得本周一的日期 String thisWeekMonday = DateUtils.parseDate2String(DateUtils.getThisWeekMonday()); //获得本月第一天的日期 String firstDay4ThisMonth = DateUtils.parseDate2String(DateUtils.getFirstDay4ThisMonth()); //今日新增会员数 Integer todayNewMember = memberDao.findMemberCountByDate(today); //总会员数 Integer totalMember = memberDao.findMemberTotalCount(); //本周新增会员数 Integer thisWeekNewMember = memberDao.findMemberCountAfterDate(thisWeekMonday); //本月新增会员数 Integer thisMonthNewMember = memberDao.findMemberCountAfterDate(firstDay4ThisMonth); //今日预约数 Integer todayOrderNumber = orderDao.findOrderCountByDate(today); //本周预约数 Integer thisWeekOrderNumber = orderDao.findOrderCountAfterDate(thisWeekMonday); //本月预约数 Integer thisMonthOrderNumber = orderDao.findOrderCountAfterDate(firstDay4ThisMonth); //今日到诊数 Integer todayVisitsNumber = orderDao.findVisitsCountByDate(today); //本周到诊数 Integer thisWeekVisitsNumber = orderDao.findVisitsCountAfterDate(thisWeekMonday); //本月到诊数 Integer thisMonthVisitsNumber = orderDao.findVisitsCountAfterDate(firstDay4ThisMonth); //热门套餐(取前4) List<Map> hotSetmeal = orderDao.findHotSetmeal(); Map<String,Object> result = new HashMap<>(); result.put("reportDate",today); result.put("todayNewMember",todayNewMember); result.put("totalMember",totalMember); result.put("thisWeekNewMember",thisWeekNewMember); result.put("thisMonthNewMember",thisMonthNewMember); result.put("todayOrderNumber",todayOrderNumber); result.put("thisWeekOrderNumber",thisWeekOrderNumber); result.put("thisMonthOrderNumber",thisMonthOrderNumber); result.put("todayVisitsNumber",todayVisitsNumber); result.put("thisWeekVisitsNumber",thisWeekVisitsNumber); result.put("thisMonthVisitsNumber",thisMonthVisitsNumber); result.put("hotSetmeal",hotSetmeal); return result; } }1.3.4 Dao接口在OrderDao和MemberDao中声明相关统计查询方法package com.yunhe.dao; import com.yunhe.pojo.Order; import java.util.List; import java.util.Map; public interface OrderDao { public void add(Order order); public List<Order> findByCondition(Order order); public Map findById4Detail(Integer id); public Integer findOrderCountByDate(String date); public Integer findOrderCountAfterDate(String date); public Integer findVisitsCountByDate(String date); public Integer findVisitsCountAfterDate(String date); public List<Map> findHotSetmeal(); }package com.yunhe.dao; import com.github.pagehelper.Page; import com.yunhe.pojo.Member; import java.util.List; public interface MemberDao { public List<Member> findAll(); public Page<Member> selectByCondition(String queryString); public void add(Member member); public void deleteById(Integer id); public Member findById(Integer id); public Member findByTelephone(String telephone); public void edit(Member member); public Integer findMemberCountBeforeDate(String date); public Integer findMemberCountByDate(String date); public Integer findMemberCountAfterDate(String date); public Integer findMemberTotalCount(); }1.3.5 Mapper映射文件在OrderDao.xml和MemberDao.xml中定义SQL语句OrderDao.xml:<!--根据日期统计预约数--> <select id="findOrderCountByDate" parameterType="string" resultType="int"> select count(id) from t_order where orderDate = #{value} </select> ​ <!--根据日期统计预约数,统计指定日期之后的预约数--> <select id="findOrderCountAfterDate" parameterType="string" resultType="int"> select count(id) from t_order where orderDate &gt;= #{value} </select> ​ <!--根据日期统计到诊数--> <select id="findVisitsCountByDate" parameterType="string" resultType="int"> select count(id) from t_order where orderDate = #{value} and orderStatus = '已到诊' </select> ​ <!--根据日期统计到诊数,统计指定日期之后的到诊数--> <select id="findVisitsCountAfterDate" parameterType="string" resultType="int"> select count(id) from t_order where orderDate &gt;= #{value} and orderStatus = '已到诊' </select> ​ <!--热门套餐,查询前4条--> <select id="findHotSetmeal" resultType="map"> select s.name, count(o.id) setmeal_count , count(o.id)/(select count(id) from t_order) proportion from t_order o inner join t_setmeal s on s.id = o.setmeal_id group by o.setmeal_id order by setmeal_count desc limit 0,4 </select>MemberDao.xml:<!--根据日期统计会员数,统计指定日期之前的会员数--> <select id="findMemberCountBeforeDate" parameterType="string" resultType="int"> select count(id) from t_member where regTime &lt;= #{value} </select> ​ <!--根据日期统计会员数--> <select id="findMemberCountByDate" parameterType="string" resultType="int"> select count(id) from t_member where regTime = #{value} </select> ​ <!--根据日期统计会员数,统计指定日期之后的会员数--> <select id="findMemberCountAfterDate" parameterType="string" resultType="int"> select count(id) from t_member where regTime &gt;= #{value} </select> ​ <!--总会员数--> <select id="findMemberTotalCount" resultType="int"> select count(id) from t_member </select>二、. 运营数据统计报表导出2.1 需求分析运营数据统计报表导出就是将统计数据写入到Excel并提供给客户端浏览器进行下载,以便体检机构管理人员对运营数据的查看和存档。2.2 提供模板文件本节我们需要将运营统计数据通过POI写入到Excel文件,对应的Excel效果如下:  通过上面的Excel效果可以看到,表格比较复杂,涉及到合并单元格、字体、字号、字体加粗、对齐方式等的设置。如果我们通过POI编程的方式来设置这些效果代码会非常繁琐。在企业实际开发中,对于这种比较复杂的表格导出一般我们会提前设计一个Excel模板文件,在这个模板文件中提前将表格的结构和样式设置好,我们的程序只需要读取这个文件并在文件中的相应位置写入具体的值就可以了。在本章节资料中已经提供了一个名为report_template.xlsx的模板文件,需要将这个文件复制到health_backend工程的template目录中2.3 完善页面在report_business.html页面提供导出按钮并绑定事件<div class="excelTitle" > <el-button @click="exportExcel">导出Excel</el-button>运营数据统计 </div>methods:{ //导出Excel报表 exportExcel(){ window.location.href = '/report/exportBusinessReport.do'; } }2.4 后台代码在ReportController中提供exportBusinessReport方法,基于POI将数据写入到Excel中并通过输出流下载到客户端。/** * 导出Excel报表 * @return */ @RequestMapping("/exportBusinessReport") public Result exportBusinessReport(HttpServletRequest request, HttpServletResponse response){ try{ //远程调用报表服务获取报表数据 Map<String, Object> result = reportService.getBusinessReport(); //取出返回结果数据,准备将报表数据写入到Excel文件中 String reportDate = (String) result.get("reportDate"); Integer todayNewMember = (Integer) result.get("todayNewMember"); Integer totalMember = (Integer) result.get("totalMember"); Integer thisWeekNewMember = (Integer) result.get("thisWeekNewMember"); Integer thisMonthNewMember = (Integer) result.get("thisMonthNewMember"); Integer todayOrderNumber = (Integer) result.get("todayOrderNumber"); Integer thisWeekOrderNumber = (Integer) result.get("thisWeekOrderNumber"); Integer thisMonthOrderNumber = (Integer) result.get("thisMonthOrderNumber"); Integer todayVisitsNumber = (Integer) result.get("todayVisitsNumber"); Integer thisWeekVisitsNumber = (Integer) result.get("thisWeekVisitsNumber"); Integer thisMonthVisitsNumber = (Integer) result.get("thisMonthVisitsNumber"); List<Map> hotSetmeal = (List<Map>) result.get("hotSetmeal"); //获得Excel模板文件绝对路径 String temlateRealPath = request.getSession().getServletContext().getRealPath("template") + File.separator + "report_template.xlsx"; //读取模板文件创建Excel表格对象 XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(new File(temlateRealPath))); XSSFSheet sheet = workbook.getSheetAt(0); XSSFRow row = sheet.getRow(2); row.getCell(5).setCellValue(reportDate);//日期 ​ row = sheet.getRow(4); row.getCell(5).setCellValue(todayNewMember);//新增会员数(本日) row.getCell(7).setCellValue(totalMember);//总会员数 ​ row = sheet.getRow(5); row.getCell(5).setCellValue(thisWeekNewMember);//本周新增会员数 row.getCell(7).setCellValue(thisMonthNewMember);//本月新增会员数 ​ row = sheet.getRow(7); row.getCell(5).setCellValue(todayOrderNumber);//今日预约数 row.getCell(7).setCellValue(todayVisitsNumber);//今日到诊数 ​ row = sheet.getRow(8); row.getCell(5).setCellValue(thisWeekOrderNumber);//本周预约数 row.getCell(7).setCellValue(thisWeekVisitsNumber);//本周到诊数 ​ row = sheet.getRow(9); row.getCell(5).setCellValue(thisMonthOrderNumber);//本月预约数 row.getCell(7).setCellValue(thisMonthVisitsNumber);//本月到诊数 ​ int rowNum = 12; for(Map map : hotSetmeal){//热门套餐 String name = (String) map.get("name"); Long setmeal_count = (Long) map.get("setmeal_count"); BigDecimal proportion = (BigDecimal) map.get("proportion"); row = sheet.getRow(rowNum ++); row.getCell(4).setCellValue(name);//套餐名称 row.getCell(5).setCellValue(setmeal_count);//预约数量 row.getCell(6).setCellValue(proportion.doubleValue());//占比 } ​ //通过输出流进行文件下载 ServletOutputStream out = response.getOutputStream(); response.setContentType("application/vnd.ms-excel"); response.setHeader("content-Disposition", "attachment;filename=report.xlsx"); workbook.write(out); out.flush(); out.close(); workbook.close(); return null; }catch (Exception e){ return new Result(false, MessageConstant.GET_BUSINESS_REPORT_FAIL,null); } } 
  • 数据库的Isolation特性
    数据库的隔离性(Isolation)核心是通过控制事务间数据的可见性、加锁策略或多版本数据管理(MVCC),减少不同事务对同一数据的干扰,不同隔离级别通过 “放宽或收紧” 这种控制力度,实现对脏读、不可重复读、幻读的不同防护效果。以 InnoDB 为例,其默认隔离级别 “可重复读(Repeatable Read)” 通过MVCC(多版本并发控制)+ 行锁 + 间隙锁的组合,避免脏读和不可重复读,但因 “当前读” 与 “快照读” 的机制差异,仍可能出现幻读。一、隔离性与隔离级别的核心逻辑:控制事务可见性隔离性的本质是解决 “多个事务同时读写同一数据时的干扰问题”,SQL 标准定义了 4 个隔离级别,从低到高对 “事务可见性” 的限制逐渐严格,对应解决的问题也不同:隔离级别解决的问题未解决的问题实现核心技术(InnoDB)读未提交(RU)无脏读、不可重复读、幻读不加锁,直接读取未提交数据读已提交(RC)脏读不可重复读、幻读行锁(写锁)+ MVCC(每次查询生成 Read View)可重复读(RR)脏读、不可重复读部分幻读MVCC(事务启动生成 Read View)+ 行锁 + Next-Key Lock串行化(Serializable)脏读、不可重复读、幻读无表锁 / 行锁(事务排队执行)低隔离级别(如 RU)性能高但数据一致性弱,高隔离级别(如串行化)一致性强但性能低,InnoDB 默认选择 “可重复读”,平衡一致性与性能。二、InnoDB 各隔离级别的实现原理:从锁与 MVCC 切入InnoDB 实现隔离性的核心技术是MVCC(多版本并发控制) 和锁机制(行锁、间隙锁、表锁),不同隔离级别通过 “是否启用 MVCC、Read View 生成时机、锁的粒度” 来区分:1. 读未提交(Read Uncommitted):无隔离,直接读未提交数据实现逻辑:事务对数据的读取不加锁,也不使用 MVCC,直接读取数据的 “最新版本”—— 即使该版本由未提交的事务修改。问题:会出现脏读(读未提交的数据)、不可重复读(同一事务内两次读同一行,结果不同)、幻读(同一条件两次读,行数不同)。场景:仅用于对一致性无要求的临时查询(如日志统计),InnoDB 中极少使用。2. 读已提交(Read Committed):避免脏读,允许不可重复读实现逻辑:结合 “行锁” 和 “MVCC”,核心是每次查询生成新的 Read View(事务可见性视图,决定哪些版本的数据可见):写事务(UPDATE/DELETE/INSERT)对修改的行加 “行锁”,未提交前其他事务无法修改,但可通过 MVCC 读 “已提交的旧版本”;读事务每次执行 SELECT 时,生成新的 Read View,只能看到 “在 Read View 生成前已提交的事务版本”,看不到未提交的版本(避免脏读);但同一事务内两次查询生成不同 Read View,若中间有其他事务修改并提交,会看到不同结果(不可重复读)。示例:事务 A 第一次查行数据为 10,事务 B 修改为 20 并提交,事务 A 第二次查看到 20(不可重复读),但不会看到事务 B 未提交时的 20(无脏读)。3. 可重复读(Repeatable Read):InnoDB 默认,避免脏读、不可重复读,仍可能幻读这是 InnoDB 的重点,通过 **“事务启动时生成 Read View + MVCC + Next-Key Lock”** 实现,需分 “快照读” 和 “当前读” 两种场景分析:4. 串行化(Serializable):完全隔离,无并发干扰实现逻辑:对所有读写操作加锁,事务按 “先来后到” 排队执行:读事务(SELECT)加 “表级共享锁”,其他事务只能读,不能写;写事务(UPDATE/DELETE/INSERT)加 “表级排他锁”,其他事务既不能读也不能写;效果:完全避免脏读、不可重复读、幻读,但并发性能极低,仅用于强一致性需求(如财务对账)。三、InnoDB 可重复读(RR)的深度解析:如何避免脏读、不可重复读,却有幻读?InnoDB 的可重复读是 “MVCC 快照读” 与 “当前读锁机制” 的结合,需先明确两个关键概念:快照读:普通 SELECT(不加锁),通过 MVCC 读取历史版本数据,生成快照;当前读:加锁的 SELECT(如SELECT ... FOR UPDATE)、UPDATE、DELETE,读取数据的最新版本,并加锁防止其他事务修改。1. 避免脏读:MVCC 的版本可见性控制脏读是 “读取到其他事务未提交的修改”,InnoDB 通过 MVCC 的Read View 版本过滤避免:InnoDB 为每条数据维护 “版本链”:每次修改数据时,会生成新的版本,旧版本存入 undo 日志,新版本记录 “修改事务 ID” 和 “指向旧版本的指针”;事务启动时生成Read View,包含 “当前活跃事务 ID 列表” 和 “最大事务 ID”;读取数据时,MVCC 会对比数据版本的 “事务 ID” 与 Read View:若数据版本的事务 ID 在 Read View 的 “活跃列表” 中(未提交),则跳过该版本,读取更早的版本;若事务 ID 不在活跃列表中(已提交),则读取该版本;结论:未提交事务的版本会被过滤,永远看不到,因此避免脏读。2. 避免不可重复读:事务全程复用 Read View不可重复读是 “同一事务内,两次读同一行,结果不同(因其他事务修改并提交)”,InnoDB 通过 **“事务启动时生成 Read View,全程复用”** 避免:读已提交(RC)的问题是 “每次查询生成新 Read View”,导致中间提交的修改可见;可重复读(RR)中,Read View 在事务第一次执行 SELECT 时生成(或事务启动时,取决于配置),之后所有查询都复用这个 Read View;即使其他事务修改数据并提交,当前事务的 Read View 仍看不到新提交的版本(因新提交的事务 ID 大于 Read View 的 “最大事务 ID”),因此两次查询看到的是同一版本,避免不可重复读。举个栗子看看:事务 A 启动,第一次 SELECT 行数据为 10,生成 Read View(活跃事务:A,最大 ID:100);事务 B(ID:101)修改该行为 20 并提交;事务 A 第二次 SELECT,因事务 B 的 ID(101)大于 Read View 的最大 ID(100),仍读取旧版本 10,无不可重复读。3. 仍可能出现幻读:快照读与当前读的差异幻读是 “同一事务内,两次执行相同条件的查询,行数不同(因其他事务插入 / 删除数据)”,InnoDB 的可重复读对快照读避免幻读,但对当前读仍可能出现幻读:(1)快照读:避免幻读普通 SELECT(快照读)时,因复用 Read View,即使其他事务插入新行并提交,新行的 “事务 ID” 大于当前事务的 Read View 最大 ID,会被过滤,因此两次查询行数相同,无幻读。示例:事务 A 启动,执行SELECT * FROM user WHERE age > 30,返回 2 行,生成 Read View(最大事务 ID:100);事务 B(ID:101)插入 1 条 age=35 的记录并提交;事务 A 再次执行相同 SELECT,因事务 B 的 ID(101)大于 Read View 的最大 ID(100),新插入的记录被过滤,仍返回 2 行,无幻读。(2)当前读:仍可能出现幻读当前读(如SELECT ... FOR UPDATE、UPDATE)会读取最新版本,并加锁,此时若其他事务插入 “符合查询条件” 的新行,会导致 “行数增加”,出现幻读:经典幻读场景:事务 A 执行SELECT * FROM user WHERE age > 30 FOR UPDATE,返回 2 行(ID=1、2),并对这两行加行锁,同时通过Next-Key Lock锁定 “age>30” 的间隙(防止插入);事务 B 尝试插入age=35的记录,因间隙锁被阻塞,无法插入(InnoDB 的 Next-Key Lock 减轻了幻读,但不是完全杜绝);若事务 B 插入的记录 “刚好在间隙外,但查询条件调整后符合”(或 Next-Key Lock 未覆盖的间隙),比如:事务 A 先执行SELECT * FROM user WHERE age BETWEEN 20 AND 30 FOR UPDATE,锁定 20-30 的间隙;事务 B 插入age=31的记录(不在 20-30 间隙内,可插入);事务 A 再执行SELECT * FROM user WHERE age > 20 FOR UPDATE,会读到事务 B 插入的 31 行,出现幻读;本质原因:当前读读取最新数据,且锁无法覆盖 “所有可能的查询条件”,若其他事务插入 “新的符合条件的行”,则会出现行数增加,即幻读。4. Next-Key Lock:减轻幻读的锁机制InnoDB 在可重复读级别下,对 “范围查询的当前读” 会使用Next-Key Lock(行锁 + 间隙锁),锁定 “满足条件的行” 和 “行之间的间隙”,防止其他事务在间隙中插入数据,从而减轻幻读:例如SELECT * FROM user WHERE id > 10 FOR UPDATE,会锁定 “id>10 的所有行”,以及 “id=10 到最大 id 的间隙”,其他事务无法在该间隙插入 id>10 的行;但 Next-Key Lock 无法覆盖 “所有可能的查询条件”(如动态调整的范围),因此仍可能出现极端场景的幻读,只是概率极低。四、总结一下下:InnoDB 隔离级别的核心逻辑隔离级别脏读不可重复读幻读核心实现(InnoDB)读未提交(RU)有有有无锁,直接读最新版本读已提交(RC)无有有行锁 + MVCC(每次查询生成 Read View)可重复读(RR)无无可能有MVCC(事务复用 Read View)+ Next-Key Lock串行化(Serializable)无无无表锁 / 行锁,事务排队InnoDB 默认的可重复读,通过 MVCC 快照读保障 “事务内数据一致性”,避免脏读和不可重复读;通过 Next-Key Lock 减少当前读的幻读风险,但因 “当前读需读最新数据”,仍无法完全杜绝幻读(需串行化级别才能彻底避免),这是 “一致性” 与 “并发性能” 的权衡结果。
  • [技术干货] 【集合贴】华为云关系数据库汇总
    华为云目前主推的数据库大致分三类:自研“云原生”架构:TaurusDB、GaussDB 系列;传统托管式 RDS:RDS for MySQL / PostgreSQL / SQL Server / MariaDB;轻量级 Flexus 版 RDS。下面把你能看到的 7 个名字一次性讲清差异、适用场景和选型建议。一、TaurusDB(云原生 MySQL 兼容版)定位华为云 2019 年自研的“云原生”OLTP 数据库,100% MySQL 8.0 协议兼容,但存储引擎、复制机制全部重写。核心差异点存算分离:计算节点共享同一份分布式存储,无需 binlog 同步。扩展性:最多 15 个只读节点,加节点分钟级完成,与数据量无关;存储 128 TB 内自动扩容。性能:官方数据百万级 QPS,复杂查询可“下推”到存储层,TP 场景 7× 于原生 MySQL。可用性:RTO<10 s;备份基于快照+redo,秒级完成。只支持 MySQL 8.0。一句话总结业务量上涨快、读写比例高、需要秒级加只读、不想做分库分表,选 TaurusDB。二、GaussDB(for MySQL / 主备版 / 分布式版)定位华为自研企业级数据库家族,分三条子产品线:GaussDB(for MySQL)外观与 TaurusDB 几乎一样(也是存算分离、MySQL 协议),但面向政府/央国企、要过等保/信创验收,内核增加全密态、国密算法、可信执行环境。GaussDB 主备版(原 GaussDB 100)自研 SQL 引擎,语法同时兼容 Oracle/MySQL/PostgreSQL 三种模式,单节点性能高,适合 Oracle 替换。GaussDB 分布式版(原 GaussDB 200)Share-Nothing MPP,PB 级分析+事务混合,用于大型 ERP、计费、数据仓库。一句话总结要信创、国密、Oracle 替换、PB 级混合负载,选 GaussDB 系列;如果只是互联网高并发,TaurusDB 更轻量。三、RDS for MySQL / PostgreSQL / SQL Server / MariaDB定位经典托管 RDS:在虚拟机里帮你装数据库、做主备、备份、补丁、监控,你拿到的是 100% 原生引擎。共同特点版本丰富:MySQL 5.6/5.7/8.0,PostgreSQL 11-15,SQL Server 2017/2019/2022,MariaDB 10.5。扩展方式:主备+最多 5 只读;加只读需要复制全量数据,时间与数据量成正比。存储上限:4 TB(部分地域 8 TB)。复制机制:主备 binlog/wal 同步,RTO≈30 s。功能完整:白名单、参数组、只读实例、代理、读写分离、备份恢复到任意一秒。差异速览MySQL:生态最大,通用 OLTP。PostgreSQL:复杂查询、JSON、GIS、时序扩展强。SQL Server:自带商业特性(SSRS、SSIS、T-SQL)、Windows 身份认证。MariaDB:比 MySQL 更激进的开源特性,部分老系统指定版本迁移用。一句话总结数据量 <4 TB、团队熟悉原生引擎、需要某个特定小版本或 Windows 生态,直接选对应 RDS。四、Flexus 云数据库 RDS定位“轻量级、低成本”子品牌,底层与 RDS for MySQL 同源,但:起步规格 1C2G,价格最低到 0.08 元/小时;控制台极简,缺省只开主节点(高备可后开);最大 1 TB 存储,性能上限 1 万 QPS 左右;适合个人博客、小程序、开发测试、毕业设计。一句话总结预算敏感、非核心系统、需要“能用就行”,选 FlexusRDS;生产业务还是回 RDS/TaurusDB。五、一张表看完所有区别维度TaurusDBGaussDB(for MySQL)RDS for MySQL/PostgreSQL/SQL Server/MariaDBFlexusRDS架构云原生存算分离同左+国密/可信传统主备精简主备引擎自研(兼容MySQL8.0)自研(兼容MySQL8.0)原生引擎原生MySQL只读节点≤15,分钟级≤15,分钟级≤5,与数据量相关≤1存储上限128 TB128 TB4/8 TB1 TB典型RTO<10 s<10 s<30 s<30 s是否信创否是否否起步价格中等高中极低适用场景高并发、快速扩容政企、Oracle替换、安全合规通用、版本特定开发测试、轻量级六、选型速决流程要信创/Oracle 语法兼容/国密 → GaussDB 系列互联网业务、读写突增、加节点要秒级、MySQL8.0 即可 → TaurusDB只要原生 MySQL/PostgreSQL/SQL Server/MariaDB,数据量 <4 TB → 对应 RDS个人/小程序/测试,预算压到最低 → FlexusRDS这样就不会再被一堆名字绕晕,按业务规模、合规、预算三步就能敲定。祝选型顺利!
  • [技术干货] 数据库死锁:高并发场景下的“幽灵”,常见场景解决办法-转载
    数据库死锁是高并发场景下的“幽灵问题”——它往往突然发生,导致业务中断,且排查起来需要结合数据库原理、日志分析和场景还原。以下内容从基础原理→诊断方法→应急解决→长效预防展开,覆盖主流数据库(SQL Server/MySQL/Oracle),帮你系统掌握死锁的应对之道。 一、先搞懂:死锁的本质与必要条件死锁是指两个或多个事务互相持有对方需要的锁,且都不愿释放,导致所有事务无限等待的状态。其发生的四个必要条件(缺一不可):互斥:资源(如行、页、表)一次只能被一个事务占用;请求与保持:事务已持有某个资源,又请求新的资源(且不释放已有资源);不可剥夺:资源不能被强制从持有事务中夺走;循环等待:事务间形成“事务A等事务B的资源,事务B等事务A的资源”的闭环。二、死锁的诊断:如何快速定位问题?诊断死锁的核心是还原“死锁环”——即找出哪些事务、访问了哪些资源、持有哪些锁、等待哪些锁。以下是各数据库的常用诊断工具和方法:1. 通用诊断步骤不管用什么数据库,诊断死锁的流程基本一致:Step 1:捕获死锁事件:开启数据库的死锁日志记录(如SQL Server的Trace Flag 1222、MySQL的innodb_print_all_deadlocks);Step 2:收集现场证据:获取死锁时的锁信息、事务历史、SQL语句;Step 3:分析死锁环:通过工具还原事务的锁请求顺序,找到循环等待的源头。2. 主流数据库的具体诊断方法(1)SQL ServerSQL Server提供了丰富的DMV(动态管理视图)和工具来诊断死锁:① 查看死锁错误日志:SQL Server的1205错误(死锁牺牲品)会记录死锁详情,可通过ERRORLOG或sys.dm_os_ring_buffers查询:-- 查询最近的死锁信息SELECT * FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = 'RING_BUFFER_DEADLOCK_CHAIN';② 用DMV还原死锁环:结合sys.dm_tran_locks(锁信息)、sys.dm_os_waiting_tasks(等待任务)、sys.dm_exec_requests(执行请求)分析:-- 查找当前死锁的事务和锁SELECT     tl.request_session_id AS spid,    tl.resource_type,    tl.resource_associated_entity_id,    tl.request_mode,    tl.request_status,    er.blocking_session_id,    er.command,    sqltext.text AS sql_statementFROM sys.dm_tran_locks tlINNER JOIN sys.dm_os_waiting_tasks w ON tl.lock_owner_address = w.resource_addressINNER JOIN sys.dm_exec_requests er ON w.session_id = er.session_idCROSS APPLY sys.dm_exec_sql_text(er.sql_handle) sqltext③ 工具辅助:Extended Events:捕获xml_deadlock_report事件,生成死锁的XML报告(可视化死锁环);SQL Profiler:跟踪死锁事件(需谨慎,性能开销大)。(2)MySQL(InnoDB)MySQL的InnoDB引擎通过SHOW ENGINE INNODB STATUS命令查看死锁信息:① 开启死锁日志:在my.cnf中设置innodb_print_all_deadlocks = ON,死锁信息会写入错误日志;② 查看死锁详情:执行SHOW ENGINE INNODB STATUS;,切换到LATEST DETECTED DEADLOCK section,会显示:死锁的两个事务的SQL语句;每个事务持有的锁(如行锁、间隙锁);等待的锁资源。(3)OracleOracle通过AWR报告或ASH分析定位死锁:① 查看死锁日志:查询V$LOCK和V$SESSION视图:-- 查找死锁的会话SELECT s.sid, s.serial#, s.username, l.type, l.id1, l.id2FROM v$lock lINNER JOIN v$session s ON l.sid = s.sidWHERE l.block = 1; -- 阻塞其他会话的锁② 生成死锁跟踪文件:设置EVENT 10046 TRACE NAME CONTEXT FOREVER, LEVEL 12,生成包含死锁详情的跟踪文件(需用TKPROF解析)。三、死锁的应急解决:先止损,再排查一旦发生死锁,需快速恢复业务,再分析根源:1. 紧急处理方法① 终止牺牲品事务:数据库会自动选择一个事务作为“牺牲品”(返回1205/1213错误),但有时需手动终止阻塞事务:SQL Server:KILL <SPID>;MySQL:KILL <CONNECTION_ID>;Oracle:ALTER SYSTEM KILL SESSION '<SID>,<SERIAL#>'。② 回滚长事务:如果某个长事务持有大量锁,主动回滚它可以快速释放资源。2. 避免“二次死锁”不要盲目重启数据库:重启会清除锁信息,但可能丢失现场;检查应用程序的重试逻辑:死锁后应用程序应指数退避重试(如等待1秒→2秒→4秒,最多3次),避免立即重试加重负载。四、死锁的长效预防:从设计到运维的闭环预防死锁的核心是破坏死锁的四个必要条件,以下是具体措施:1. 设计阶段:从源头减少死锁可能① 减少事务粒度:将大事务拆分为小事务(如批量更新拆成逐条或分批次),缩短锁的持有时间。例如:❌ 坏实践:UPDATE table SET col=1 WHERE id IN (1..10000);(持有大量锁);✅ 好实践:循环更新100条/批,每批提交一次。② 统一资源访问顺序:所有事务都按相同的顺序访问表或行(如先访问表A再访问表B,不要有的事务先A后B,有的先B后A)。例如:事务1:更新表X→更新表Y;事务2:必须也更新表X→更新表Y(避免循环等待)。③ 避免长事务:不要在事务中做无关操作(如查询大量数据、调用外部API、等待用户输入),这些操作会延长锁的持有时间。 2. 技术手段:用数据库特性降低死锁概率① 选择合适的隔离级别:高隔离级别(如SQL Server的Serializable、MySQL的Repeatable Read)会增加锁的竞争,尽量使用读已提交快照隔离(RCSI)或乐观并发:SQL Server:开启READ_COMMITTED_SNAPSHOT,事务读取时用行版本控制,不持有共享锁;MySQL:使用READ COMMITTED隔离级别(减少间隙锁);Oracle:默认的READ COMMITTED+行版本控制(Undo表空间)。② 使用乐观锁:用版本号或时间戳代替悲观锁,避免长时间持有排他锁。例如:表结构增加version字段,更新时检查版本:UPDATE table SET col=1, version=version+1 WHERE id=123 AND version=old_version;AI写代码sql123如果更新失败(版本号变了),说明数据已被修改,应用程序重试即可。③ 优化索引:缺少索引会导致全表扫描,获取更多锁(如更新一个无索引的列,会锁整行甚至整表)。确保:WHERE条件中的列有索引;连接条件中的列有索引;避免索引失效(如函数转换、类型隐式转换)。3. 运维层面:监控与预警① 实时监控锁等待:用Prometheus+Grafana或数据库自带工具监控锁指标:SQL Server:sys.dm_os_waiting_tasks(等待任务数)、sys.dm_tran_locks(锁持有数);MySQL:SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%'(行锁等待数、超时数);Oracle:V$LOCK(锁数量)、V$SESSION_WAIT(等待事件)。② 设置死锁告警:当死锁次数超过阈值(如1分钟1次)时,触发邮件/钉钉告警,及时排查。4. 测试阶段:模拟高并发场景用JMeter/LoadRunner模拟高并发请求,提前暴露死锁问题;对核心业务流程做压力测试,验证锁竞争情况。五、常见死锁场景与解决方法以下是高频死锁场景及针对性解决方案: 1. 交叉更新死锁场景:事务1更新行A→更新行B;事务2更新行B→更新行A,形成循环等待。解决:统一资源访问顺序(如都先更新A再更新B)。2. 间隙锁死锁(MySQL特有)场景:MySQL的RR隔离级别下,更新非唯一索引列会加间隙锁(锁定范围内的空闲行),多个事务的间隙锁重叠导致死锁。解决:升级到RC隔离级别(禁用间隙锁);优化查询条件,使用唯一索引;减少事务的持有时间。3. 外键约束死锁场景:主表删除行时,会锁子表的对应行;如果子表有未提交的事务,主表删除会被阻塞,进而导致死锁。解决:禁用外键约束(不推荐,破坏数据一致性);先删除子表相关行,再删除主表行;使用ON DELETE CASCADE自动级联删除。 六、总结:吃一堑长一智的关键死锁的本质是资源竞争的闭环,预防的核心是减少竞争、统一顺序、缩短锁持有时间。记住以下几点:日志是关键:开启死锁日志记录,快速定位问题;设计优先:从事务粒度、访问顺序、索引优化入手,减少死锁可能;监控兜底:实时监控锁指标,提前预警;重试机制:应用程序必须有死锁重试逻辑,避免业务中断。通过以上体系化的方法,你可以从“被动救火”转向“主动预防”,大幅降低死锁的发生概率——毕竟,最好的解决是让死锁永远不会发生。————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/ChailangCompany/article/details/153269415
  • [技术干货] 华为云高斯数据库实操与案例分享:开启企业级数据管理新篇章
    在数字化转型的浪潮中,数据已成为企业的核心资产。如何高效、安全、智能地管理和利用海量数据,是每个企业都必须面对的课题。华为云高斯数据库(GaussDB)作为一款领先的企业级分布式数据库,因其高性能、高可用、高安全及全栈自主创新等特性,正受到越来越多企业的青睐。本文将结合实操步骤与典型案例,带您深入了解高斯数据库的强大能力。一、 高斯数据库核心特性简介在进入实操之前,我们首先需要理解高斯数据库的“杀手锏”:存算分离架构: 计算节点与存储节点解耦,支持秒级弹性伸缩,可按需配置资源,极大降低成本。分布式强一致性: 在分布式部署下,依然保证数据的强一致性,确保业务逻辑准确无误。高可用与高可靠: 同城双活、异地容灾等多重高可用方案,RTO(恢复时间目标)和RPO(恢复点目标)近乎为零。全栈安全: 从芯片、硬件到软件、云服务的全栈安全防护,并提供数据加密、动态脱敏等高级安全特性。AI-Native内核: 将AI技术融入数据库内核,实现智能参数调优、智能索引推荐等,提升运维效率。二、 实操演练:从零开始使用高斯DB我们将以最常用的GaussDB(for MySQL) 为例,演示如何快速创建并连接一个数据库实例。步骤一:购买与配置实例登录华为云控制台,在产品列表中找到“数据库 > 云数据库 GaussDB”。点击“购买数据库实例”,进入配置页面。选择实例规格:数据库引擎: 选择“GaussDB(for MySQL)”。架构: 根据业务需求选择“主备”或“集群”(分布式)。CPU/内存: 根据业务负载选择,例如2 vCPUs / 4 GiB。存储空间: 选择SSD云盘,初始可选择100GB。设置网络与密码:选择或新建一个虚拟私有云(VPC)和子网,这是确保应用安全访问数据库的关键。设置数据库管理员账号(如root)的密码。确认订单并支付,等待约5-10分钟,实例状态变为“正常”即表示创建成功。步骤二:通过DAS连接并操作数据库华为云提供了便捷的数据管理服务(DAS),无需安装客户端,即可在浏览器中操作数据库。在实例列表中找到刚创建的实例,点击实例名称进入详情页。在左侧导航栏点击“登录”,选择“DAS登录”。输入创建时设置的密码,即可进入SQL操作界面。步骤三:执行基础SQL命令在DAS的SQL窗口中,我们可以执行标准的SQL语句来创建数据库、表和数据进行测试。sql 复制 下载-- 创建一个测试数据库CREATE DATABASE test_gaussdb;USE test_gaussdb;-- 创建一张用户表CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);-- 插入测试数据INSERT INTO users (name, email) VALUES ('张三', 'zhangsan@example.com'),('李四', 'lisi@example.com');-- 查询数据SELECT * FROM users;执行成功后,您将看到插入的数据被正确返回。至此,您已经完成了高斯数据库最基础的创建、连接和操作流程。三、 案例分享:某大型制造企业的数据中台实践背景: 国内某大型制造企业,其ERP、MES、SCM等核心系统长期运行在传统商业数据库上,面临成本高昂、性能瓶颈和运维复杂三大难题。挑战:“分库分表”方案复杂,应用改造成本高。“双十一”式业务高峰期间,系统响应缓慢,影响生产排程。国外数据库License费用持续上涨,供应链风险加剧。解决方案:该企业选择华为云GaussDB作为其数据中台的统一数据存储引擎。平滑迁移: 利用华为云的数据复制服务(DRS),将原有数据库中的数据在线迁移至GaussDB(for openGauss)集群,迁移过程对业务影响极小。分布式架构: 采用GaussDB的分布式能力,将数十TB的业务数据自动分片到多个数据节点上,彻底解决了单机性能瓶颈。HTAP混合负载: 利用GaussDB的HTAP能力,一套数据库同时支撑前端业务的联机交易(OLTP)和后端管理层的实时分析报表(OLAP),省去了传统ETL流程,让决策者能看到分钟级的最新业务数据。成效:性能提升: 核心交易系统响应时间从秒级降至毫秒级,高峰时段运行平稳。成本优化: 总体拥有成本(TCO)相比原有方案下降超过50%。运维简化: 华为云提供了完善的监控、备份、弹性伸缩能力,企业数据库团队从繁重的日常运维中解放出来,更专注于业务创新。四、 总结通过以上实操和案例可以看出,华为云高斯数据库不仅技术先进、功能强大,更能通过其云原生特性,为企业提供极致的易用性和弹性。从简单的Web应用到复杂的核心业务系统,GaussDB都能提供坚实的数据底座支撑。对于寻求数字化转型、构建自主可控IT架构的企业而言,深入学习和应用华为云高斯数据库,无疑是一个极具战略价值的选择。建议读者可以亲自在华为云官网申请免费试用,通过实践来感受其魅力。
  • [问题求助] ECPG连接高斯数据库,测试环境正常访问,生产环境报-402无法连接数据库
    ECPG连接高斯数据库,测试环境正常访问,生产环境报-402无法连接数据库(ecpg需要头文件,库文件从数据库服务器/lib,/liclude获取,ecpg编译/链接均无问题))。生产环境telnet高斯数据库端口正常,gsql访问高斯数据库正常,ecpg 用户名/密码/数据库正确,数据库服务器未发现用户密码错等日志,测试和生产高斯数据库版本一致,建库脚本一致,麻烦大佬帮忙分析分析,可能是哪方面的问题,在此谢过了。
  • [数据库使用] extra_float_digits导致的double类型模糊匹配错误
    问题背景:select * from schema.tablename where  double::text like '%114.0061705';查询结果为空,但实际有符合条件的数据。根因:double类型的数值,在进行模糊查询时,由于extra_float_digits导致精度不一致,结果集出现误差。 extra_float_digits参数说明:调整浮点值显示的数据位数,浮点类型包括float4、float8 以及几何数据类型。参数值加在标准的数据位数上(FLT_DIG或DBL_DIG中合适的)。参数类型:USERSET取值范围:整型,-15~3说明:设置为3,表示包括部分有效的数据位。对转储需要精确恢复的浮点数据尤其有用。设置为负数,表示摒弃不需要的数据位。默认值:0 通过JDBC进行连接DWS时,需要注意。连接参数第三方工具通过JDBC连接时,JDBC向DWS发起连接请求,会默认添加以下配置参数,详见JDBC代码ConnectionFactoryImpl类的实现。params = { { "user", user }, { "database", database }, { "client_encoding", "UTF8" }, { "DateStyle", "ISO" }, { "extra_float_digits", "3" }, { "TimeZone", createPostgresTimeZone() }, };这些参数可能会导致JDBC客户端的行为与gsql客户端的行为不一致,例如,Date数据显示方式、浮点数精度表示、timezone显示。如果实际期望和这些配置不符,建议在java连接设置代码中显式设定这些参数。通过JDBC连接数据库时,会设置extra_float_digits=3,DWS中设置为extra_float_digits=0,可能会造成同一条数据在JDBC显示和gsql显示的精度不同。对于精度敏感的场景,建议使用numeric类型。
  • [问题求助] 数据迁移
    目前我想做迁移的实验,需要在Oracle和MySQL创建大量的表,索引等对象,有什么方式可以模拟真实的生产环境吗
  • [技术干货] 大数据干货合集(2025年9月)
    锁相关视图cid:link_1锁相关参数介绍cid:link_2自动处理单点死锁cid:link_3分布式死锁cid:link_4pooler连接池详解cid:link_5postgres工作线程cid:link_6Pooler 连接池复用流程cid:link_7Pooler 连接清理cid:link_8Stream 线程cid:link_0idleRing的作用cid:link_9数据结构设计cid:link_10Stream 线程状态转移DFA设计cid:link_11单个Stream线程执行流程cid:link_12通过表象看Stream线程池逻辑cid:link_13建立多用户场景https://bbs.huaweicloud.com/forum/thread-0243194537401613171-1-1.html
  • [技术干货] 建立多用户场景
    Create user ***;(建立多用户) 分别执行带Stream算子的查询;(参考场景一示例) 查询结束,查pgxc_thread_wait_status看DN节点:预期Stream线程状态为wait thread cond。且多user之间Stream线程可以复用。 例:用户一执行完查询,视图中显示共有四个Stream线程在线程池,用户二执行同样查询返回正确结果,视图中的Stream线程个数不变,且线程号也是一致的,则说明复用。 集群基础行为场景——线程清理场景 调整guc 参数 max_stream_pool 的值,观测是否生效;预期:当设置max_stream_pool 小于当前 idle 线程个数,支持线程个数实时减少;当设置max_stream_pool大于当前idle线程个数,将由业务驱动线程个数的增加,但是不会超过max_stream_pool。 执行 clean connection(ALL force),查看 Stream 线程是否被清理;预期:该database 的Stream线程被完全清理。 执行drop database命令,查看Stream线程是否被清理;预期:该database的Stream 线程被完全清理。 
  • [技术干货] 通过表象看Stream线程池逻辑
    max_stream_pool:设置 Stream线程池能够容纳Stream线程的最大个数。该参数8.1.2 及以上版本支持。默认值为65535。设置为-1表示不开启Stream线程池。该参数支持reload更新,更新规则:设置max_stream_pool小于当前可用线程个数,支持线程个数实时减少;当设置max_stream_pool大于当前idle线程个数,将由业务驱动线程个数的增加。wait stream task:空闲的Stream线程; wait node:等待其他DN的数据,需要关注对端状态; flush data:发送数据给其他DN时因为对端buffer满而阻塞; wait cmd:DN上空闲的postgres线程,等待CN的下一个query; none:未定义状态,极有可能是阻塞原因; synchronize quit:同步退出状态,自身任务已完成,在等待同一个query的其他线程一起退出。Create database ***;(建立多库) 分别执行带Stream算子的查询; 查询结束,查pgxc_thread_wait_status看DN节点:预期Stream线程状态为wait thread cond。且多database之间Stream线程不复用。 例:create table test_01(c1 int, c2 int)with(orientation=column) distribute by hash(c1); insert into test_01 select generate_series(1,100), generate_series(1,100);analyze test_01; select * from test_01 a, test_01 b, test_01 c, test_01 d, test_01 e, test_01 f where a.c2 =b.c2 and c.c2 = d.c2 and e.c2=f.c2 limit 100;
  • [技术干货] 单个Stream线程执行流程
    Stream 线程初始化仅初始化一次,执行完query之后,便将连接归还到连接池里,循环执行上图中黄色部分的语句,如果有异常则线程退出,连接销毁,slot 归还至emptyRing;如果正常执行结束,将连接中内容清理,避免下个连接误用,并将slot归还至idleRing 等待下个连接复用。 那么Stream线程复用时如何保持参数的一致性呢,对应上图中的set GUC params阶段。父线程保存自己的guc_variables在syncGucVariables中,syncGucVariables是需要传递给 Stream 的结构用以保证父子线程 guc 参数的一致。然后父线程在初始化StreamProducer 时将 syncGucVariables 保存在该结构中传递。Stream 线程根据StreamProducer 初始化自己的 syncGucVariables 变量,首先 reset 所有的 guc 变量,然后根据syncGucVariables修正自己的variables。 STREAM_SLOT_EXIT:idleRing(idle 线程超时)、emptyRing(初始化或者FATAL); STREAM_SLOT_IDLE:idleRing STREAM_SLOT_HOLD:运行空间(从无锁队列中取出)、idleRing(idle线程超时或中断); STREAM_SLOT_RUN:运行空间。 根据各状态所处的位置情况,从idleRing中取出的slot可能有三种状态:EXIT、IDLE、HOLD。当取出IDLE状态的slot,说明线程可复用;当取出EXIT状态的slot,说明线程已退出,此时需要将slot转存到emptyRing;当取出HOLD状态,说明线程正在被使用,此时需要放回idleRing。 
  • [技术干货] Stream 线程状态转移DFA设计
    每一个记录线程信息的结构ThreadSlot中都保存了线程当前的状态status,记录线程状态的目的是为了保障线程执行过程的有序控制,也可以通过状态的互斥避免threadSlot 不会被两个线程同时使用。 Stream 线程状态转移用确定性有限状态机(DFA,definite automata)表征,共包含 4 个状态:STREAM_SLOT_EXIT 、 STREAM_SLOT_IDLE 、STREAM_SLOT_HOLD和STREAM_SLOT_RUN状态。其物理含义如下: STREAM_SLOT_EXIT:线程退出状态,表示线程未被创建或线程已退出; STREAM_SLOT_IDLE:线程可复用状态,表示线程在idleRing中,可以被复用; STREAM_SLOT_HOLD:线程临时独占状态,表示线程在做进入下一个状态的准备工作; STREAM_SLOT_RUN:线程运行状态,表示线程正在执行任务。与状态对应的,是 slot 所处的位置,slot 所处的位置有三处,分别是 idleRing、emptyRing和运行空间,slot从无锁队列中拿出,运行时所处的位置,我们称之为运行空间。
总条数:1630 到第
上滑加载中