数据库Hash Join算法演示
本动画展示了Hash Join算法的两个阶段:构建阶段(使用小表构建哈希表)和探查阶段(使用大表探测哈希表)
<div class="container">
<div class="tables-container">
<div class="table-section">
<div class="table-title">小表 (Build Side)</div>
</div>
<div class="table-section">
<div class="table-title">大表 (Probe Side)</div>
</div>
</div>
<canvas id="hashJoinCanvas" width="1000" height="600"></canvas>
<div class="controls">
<button id="startBtn">开始演示</button>
<button id="buildBtn" disabled>构建阶段</button>
<button id="probeBtn" disabled>探查阶段</button>
<button id="resetBtn">重置</button>
</div>
<div id="status" class="status"></div>
<div class="legend">
<div class="legend-item">
<div class="color-box" style="background-color: #3498db;"></div>
<span>小表 (构建表)</span>
</div>
<div class="legend-item">
<div class="color-box" style="background-color: #e74c3c;"></div>
<span>大表 (探查表)</span>
</div>
<div class="legend-item">
<div class="color-box" style="background-color: #2ecc71;"></div>
<span>哈希表</span>
</div>
<div class="legend-item">
<div class="color-box" style="background-color: #f39c12;"></div>
<span>匹配结果</span>
</div>
<div class="legend-item">
<div class="color-box" style="background-color: #9b59b6;"></div>
<span>当前处理元素</span>
</div>
</div>
</div>
<script>
// 获取Canvas和上下文
const canvas = document.getElementById('hashJoinCanvas');
const ctx = canvas.getContext('2d');
// 获取按钮和状态元素
const startBtn = document.getElementById('startBtn');
const buildBtn = document.getElementById('buildBtn');
const probeBtn = document.getElementById('probeBtn');
const resetBtn = document.getElementById('resetBtn');
const statusEl = document.getElementById('status');
// 定义表数据
const smallTable = [
{ id: 101, name: 'Alice', hash: 1 },
{ id: 102, name: 'Bob', hash: 2 },
{ id: 103, name: 'Charlie', hash: 3 },
{ id: 104, name: 'David', hash: 0 }
];
const largeTable = [
{ id: 101, value: 'Value A', hash: 1 },
{ id: 105, value: 'Value B', hash: 1 },
{ id: 102, value: 'Value C', hash: 2 },
{ id: 106, value: 'Value D', hash: 2 },
{ id: 103, value: 'Value E', hash: 3 },
{ id: 104, value: 'Value F', hash: 0 }
];
// 动画状态
let animationState = {
phase: 'idle', // 'idle', 'build', 'probe'
buildIndex: 0,
probeIndex: 0,
hashTable: [[], [], [], []], // 4个哈希桶
matches: [],
currentHash: null,
animationId: null
};
// 颜色定义
const colors = {
smallTable: '#3498db',
largeTable: '#e74c3c',
hashTable: '#2ecc71',
match: '#f39c12',
current: '#9b59b6',
text: '#2c3e50',
border: '#bdc3c7'
};
// 绘制初始状态
function drawInitialState() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制标题
ctx.fillStyle = colors.text;
ctx.font = 'bold 20px Arial';
ctx.fillText('Hash Join 算法演示', canvas.width/2 - 100, 30);
// 绘制小表
drawTable(smallTable, 50, 80, colors.smallTable, '小表 (Build Side)');
// 绘制大表
drawTable(largeTable, 550, 80, colors.largeTable, '大表 (Probe Side)');
// 绘制哈希表结构
drawHashTable(300, 350);
// 绘制结果区域
drawResultArea(50, 500);
// 更新状态文本
statusEl.textContent = '点击"开始演示"查看Hash Join算法过程';
}
// 绘制表格
function drawTable(table, x, y, color, title) {
// 绘制标题
ctx.fillStyle = colors.text;
ctx.font = 'bold 16px Arial';
ctx.fillText(title, x, y - 10);
// 绘制表头
ctx.fillStyle = color;
ctx.fillRect(x, y, 400, 30);
ctx.fillStyle = 'white';
ctx.font = '14px Arial';
ctx.fillText('ID', x + 10, y + 20);
ctx.fillText(table === smallTable ? 'Name' : 'Value', x + 100, y + 20);
ctx.fillText('Hash', x + 300, y + 20);
// 绘制表格行
for (let i = 0; i < table.length; i++) {
const rowY = y + 30 + i * 30;
// 高亮当前处理的行
if ((animationState.phase === 'build' && i === animationState.buildIndex && table === smallTable) ||
(animationState.phase === 'probe' && i === animationState.probeIndex && table === largeTable)) {
ctx.fillStyle = colors.current;
} else {
ctx.fillStyle = i % 2 === 0 ? '#f8f9fa' : '#e9ecef';
}
ctx.fillRect(x, rowY, 400, 30);
ctx.strokeStyle = colors.border;
ctx.strokeRect(x, rowY, 400, 30);
ctx.fillStyle = colors.text;
ctx.font = '14px Arial';
ctx.fillText(table[i].id.toString(), x + 10, rowY + 20);
ctx.fillText(table === smallTable ? table[i].name : table[i].value, x + 100, rowY + 20);
ctx.fillText(table[i].hash.toString(), x + 300, rowY + 20);
}
}
// 绘制哈希表
function drawHashTable(x, y) {
// 绘制标题
ctx.fillStyle = colors.text;
ctx.font = 'bold 16px Arial';
ctx.fillText('哈希表', x, y - 10);
// 绘制哈希桶
for (let i = 0; i < 4; i++) {
const bucketX = x + i * 150;
// 绘制桶标题
ctx.fillStyle = colors.hashTable;
ctx.fillRect(bucketX, y, 120, 30);
ctx.fillStyle = 'white';
ctx.font = '14px Arial';
ctx.fillText(`桶 ${i}`, bucketX + 50, y + 20);
// 绘制桶内容
const bucket = animationState.hashTable[i];
for (let j = 0; j < bucket.length; j++) {
const itemY = y + 30 + j * 30;
ctx.fillStyle = j % 2 === 0 ? '#f8f9fa' : '#e9ecef';
ctx.fillRect(bucketX, itemY, 120, 30);
ctx.strokeStyle = colors.border;
ctx.strokeRect(bucketX, itemY, 120, 30);
ctx.fillStyle = colors.text;
ctx.font = '14px Arial';
ctx.fillText(`ID: ${bucket[j].id}`, bucketX + 10, itemY + 20);
}
// 如果是当前哈希桶,高亮显示
if (animationState.currentHash === i) {
ctx.strokeStyle = colors.current;
ctx.lineWidth = 3;
ctx.strokeRect(bucketX - 2, y - 2, 124, 32 + bucket.length * 30);
ctx.lineWidth = 1;
}
}
}
// 绘制结果区域
function drawResultArea(x, y) {
// 绘制标题
ctx.fillStyle = colors.text;
ctx.font = 'bold 16px Arial';
ctx.fillText('连接结果', x, y - 10);
// 绘制表头
ctx.fillStyle = colors.match;
ctx.fillRect(x, y, 900, 30);
ctx.fillStyle = 'white';
ctx.font = '14px Arial';
ctx.fillText('小表ID', x + 10, y + 20);
ctx.fillText('小表Name', x + 100, y + 20);
ctx.fillText('大表ID', x + 300, y + 20);
ctx.fillText('大表Value', x + 400, y + 20);
ctx.fillText('匹配哈希值', x + 700, y + 20);
// 绘制匹配结果
for (let i = 0; i < animationState.matches.length; i++) {
const match = animationState.matches[i];
const rowY = y + 30 + i * 30;
ctx.fillStyle = i % 2 === 0 ? '#f8f9fa' : '#e9ecef';
ctx.fillRect(x, rowY, 900, 30);
ctx.strokeStyle = colors.border;
ctx.strokeRect(x, rowY, 900, 30);
ctx.fillStyle = colors.text;
ctx.font = '14px Arial';
ctx.fillText(match.small.id.toString(), x + 10, rowY + 20);
ctx.fillText(match.small.name, x + 100, rowY + 20);
ctx.fillText(match.large.id.toString(), x + 300, rowY + 20);
ctx.fillText(match.large.value, x + 400, rowY + 20);
ctx.fillText(match.hash.toString(), x + 700, rowY + 20);
}
}
// 构建阶段动画
function animateBuildPhase() {
if (animationState.buildIndex >= smallTable.length) {
// 构建阶段完成
animationState.phase = 'idle';
buildBtn.disabled = false;
probeBtn.disabled = false;
statusEl.textContent = '构建阶段完成!哈希表已建立。点击"探查阶段"继续。';
return;
}
// 更新状态
animationState.phase = 'build';
const currentItem = smallTable[animationState.buildIndex];
animationState.currentHash = currentItem.hash;
// 将当前项添加到哈希表
animationState.hashTable[currentItem.hash].push(currentItem);
// 绘制当前状态
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawTable(smallTable, 50, 80, colors.smallTable, '小表 (Build Side)');
drawTable(largeTable, 550, 80, colors.largeTable, '大表 (Probe Side)');
drawHashTable(300, 350);
drawResultArea(50, 500);
// 更新状态文本
statusEl.textContent = `构建阶段: 处理小表记录 ID=${currentItem.id}, 哈希值=${currentItem.hash}`;
animationState.buildIndex++;
// 继续动画
animationState.animationId = setTimeout(animateBuildPhase, 1500);
}
// 探查阶段动画
function animateProbePhase() {
if (animationState.probeIndex >= largeTable.length) {
// 探查阶段完成
animationState.phase = 'idle';
buildBtn.disabled = false;
probeBtn.disabled = false;
statusEl.textContent = 'Hash Join 完成!所有记录已处理。';
return;
}
// 更新状态
animationState.phase = 'probe';
const currentItem = largeTable[animationState.probeIndex];
animationState.currentHash = currentItem.hash;
// 在哈希表中查找匹配项
const hashBucket = animationState.hashTable[currentItem.hash];
let foundMatch = false;
for (const smallItem of hashBucket) {
if (smallItem.id === currentItem.id) {
// 找到匹配
animationState.matches.push({
small: smallItem,
large: currentItem,
hash: currentItem.hash
});
foundMatch = true;
break;
}
}
// 绘制当前状态
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawTable(smallTable, 50, 80, colors.smallTable, '小表 (Build Side)');
drawTable(largeTable, 550, 80, colors.largeTable, '大表 (Probe Side)');
drawHashTable(300, 350);
drawResultArea(50, 500);
// 更新状态文本
if (foundMatch) {
statusEl.textContent = `探查阶段: 处理大表记录 ID=${currentItem.id}, 哈希值=${currentItem.hash} - 找到匹配!`;
} else {
statusEl.textContent = `探查阶段: 处理大表记录 ID=${currentItem.id}, 哈希值=${currentItem.hash} - 未找到匹配`;
}
animationState.probeIndex++;
// 继续动画
animationState.animationId = setTimeout(animateProbePhase, 1500);
}
// 重置动画
function resetAnimation() {
// 清除任何正在进行的动画
if (animationState.animationId) {
clearTimeout(animationState.animationId);
}
// 重置状态
animationState = {
phase: 'idle',
buildIndex: 0,
probeIndex: 0,
hashTable: [[], [], [], []],
matches: [],
currentHash: null,
animationId: null
};
// 启用/禁用按钮
startBtn.disabled = false;
buildBtn.disabled = true;
probeBtn.disabled = true;
// 重绘初始状态
drawInitialState();
}
// 事件监听
startBtn.addEventListener('click', function() {
startBtn.disabled = true;
buildBtn.disabled = false;
statusEl.textContent = '准备开始构建阶段...';
});
buildBtn.addEventListener('click', function() {
buildBtn.disabled = true;
probeBtn.disabled = true;
animateBuildPhase();
});
probeBtn.addEventListener('click', function() {
buildBtn.disabled = true;
probeBtn.disabled = true;
animateProbePhase();
});
resetBtn.addEventListener('click', resetAnimation);
// 初始化
drawInitialState();
</script>
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




