Una implementación de base de datos simple de MIT6.830 lab5


prefacio

Este laboratorio es para implementar el índice de árbol B+, BTreeFile consta de cuatro páginas diferentes.

En primer lugar, los nodos del árbol tienen dos tipos diferentes de páginas: BTreeInternalPage y BTreeLeafPage ;

También está la página del nodo principal BTreeHeaderPage para rastrear qué páginas del archivo se están utilizando

Finalmente, hay un nodo raíz de árbol B+ BTreeRootPtrPage

código completo


1. ¿Sobre lab5?

En esta práctica de laboratorio, implementará un índice de árbol B+ para realizar búsquedas y escaneos de rango eficientes. Le proporcionamos todo el código de bajo nivel que necesitará para implementar la estructura de árbol. Implementará la búsqueda, la división de páginas,
la redistribución de tuplas entre páginas y la fusión de páginas.
Puede resultarle útil revisar las secciones 10.3 a 10.7 del libro de texto, que
brindan información detallada sobre la estructura de los árboles B+, así como el pseudocódigo para búsquedas, inserciones y eliminaciones.
Como se describe en el libro de texto y se analiza en clase, los nodos internos en los árboles B+ contienen varias entradas, cada una de las cuales consiste en un valor clave y un puntero secundario izquierdo y derecho. Las claves adyacentes comparten un puntero secundario, por lo que los nodos internos
que contienen m claves tienen m+1 punteros secundarios. Los nodos hoja pueden contener
entradas de datos o punteros a entradas de datos en otros archivos de base de datos. Para
simplificar, implementaremos un árbol B+ en el que las páginas hoja contienen las entradas de datos. Las páginas de hoja adyacentes están vinculadas entre sí con punteros hermanos derecho e izquierdo, por lo que los escaneos de rango solo requieren una búsqueda inicial a través de la raíz y los nodos internos para encontrar la primera página de hoja. Las páginas de hoja subsiguientes se encuentran siguiendo los punteros hermanos de la derecha (o de la izquierda).

dirección del curso

dirección del laboratorio

dos, laboratorio5

1.Ejercicio 1

Implemente el método findLeafPage() en BTreeFile, es decir, busque la página hoja correspondiente BTreeLeafPage de acuerdo con el campo proporcionado. Se resuelve una consulta recursiva. En casos especiales, si el valor proporcionado es nulo, se repetirá en el hijo más a la izquierda cada vez. Aquí, el BTreeFile.getPage() proporcionado por el laboratorio se usa para obtener la página. Este método requiere un parámetro adicional para rastrear la lista de páginas sucias, el siguiente ejercicio necesita usar este parámetro.

Vale la pena señalar que la iteración de BTreeInternalPage se itera en unidades de BTreeEntry.BTreeEntry contiene la clave en el nodo interno, el BTreePageId del elemento secundario izquierdo y el BTreePageId del elemento secundario derecho.

	private BTreeLeafPage findLeafPage(TransactionId tid, Map<PageId, Page> dirtypages, BTreePageId pid, Permissions perm,
                                       Field f)
					throws DbException, TransactionAbortedException {
    
    
		// some code goes here

		int type = pid.pgcateg();
		if(type == BTreePageId.LEAF){
    
    
			return (BTreeLeafPage) getPage(tid,dirtypages,pid,perm);
		}
		BTreeInternalPage internalPage = (BTreeInternalPage) getPage(tid,dirtypages,pid,Permissions.READ_ONLY);
		Iterator<BTreeEntry> it = internalPage.iterator();
		BTreeEntry entry = null;
		while (it.hasNext()){
    
    
			entry = it.next();
			if(f == null){
    
    
				return findLeafPage(tid,dirtypages,entry.getLeftChild(),perm,f);
			}
			if(entry.getKey().compare(Op.GREATER_THAN_OR_EQ,f)){
    
    
				return findLeafPage(tid,dirtypages, entry.getLeftChild(), perm,f);
			}
		}
		return findLeafPage(tid,dirtypages, entry.getRightChild(), perm,f);
	}

2.Ejercicio 2

Implemente los métodos splitLeafPage() y splitInternalPage(). findLeafPage() se puede usar para encontrar la página hoja correcta donde debemos insertar la tupla. Sin embargo, cada página tiene un número limitado de ranuras, y necesitamos poder insertar tuplas incluso si la página hoja correspondiente está llena. Tal intento de insertar una tupla en una página de hoja completa haría que esa página se dividiera para que las tuplas se distribuyeran uniformemente entre las dos páginas nuevas.

Cada vez que se divide una página hoja, se debe agregar al nodo principal una nueva entrada correspondiente a la primera tupla en la segunda página. De vez en cuando, un nodo interno también puede estar lleno y no puede aceptar nuevas entradas. En este caso, el padre debe dividirse y agregar una nueva entrada a su padre, lo que puede causar una división recursiva y eventualmente crear un nuevo nodo raíz.

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
Primero implemente el nodo de hoja dividida:

	public BTreeLeafPage splitLeafPage(TransactionId tid, Map<PageId, Page> dirtypages, BTreeLeafPage page, Field field)
			throws DbException, IOException, TransactionAbortedException {
    
    
		// some code goes here
        //
        // Split the leaf page by adding a new page on the right of the existing
		// page and moving half of the tuples to the new page.  Copy the middle key up
		// into the parent page, and recursively split the parent as needed to accommodate
		// the new entry.  getParentWithEmtpySlots() will be useful here.  Don't forget to update
		// the sibling pointers of all the affected leaf pages.  Return the page into which a 
		// tuple with the given key field should be inserted.

		//1、通过getEmptyPage创建一个newRightPage,
		BTreeLeafPage newRightPage = (BTreeLeafPage) getEmptyPage(tid,dirtypages,BTreePageId.LEAF);

		//2、将当前page中一半的tuple插入到newRightPage中。插入时应该先从page中删除tuple,然后再插入到newRightPage。
		// (newRightPage插入tuple后会给其赋值新的recordId,page删除tuple时根据其recordId进行查找然后删除,而page无法定位到被赋值了新recordId的tuple,则无法将其删除)。
		int tuplesNum = page.getNumTuples();
		Iterator<Tuple> it = page.reverseIterator();
		for (int i=0; i<tuplesNum / 2; i++){
    
    
			Tuple tuple = it.next();
			page.deleteTuple(tuple);
			newRightPage.insertTuple(tuple);
		}

		//3、如果当前page有右兄弟oldRightPage,将oldRightPage左兄弟的指针指向newRightPage,
		// 将newRightPage的右兄弟指针指向oldRightPage。并将oldRightPage添加到dirtypages中。
		BTreePageId oldRightPageId = page.getRightSiblingId();
		BTreeLeafPage oldRightPage = oldRightPageId == null ? null : (BTreeLeafPage) getPage(tid,dirtypages,oldRightPageId,Permissions.READ_ONLY);
		if(oldRightPage != null){
    
    
			oldRightPage.setLeftSiblingId(newRightPage.getId());
			newRightPage.setRightSiblingId(oldRightPageId);
			dirtypages.put(oldRightPageId,oldRightPage);
		}

		//4、将page的右兄弟指针指向newRightPage,newRightPage的左兄弟指针指向page。将page、newRightPage添加到dirtypages中。
		page.setRightSiblingId(newRightPage.getId());
		newRightPage.setLeftSiblingId(page.getId());
		dirtypages.put(page.getId(),page);
		dirtypages.put(newRightPage.getId(),newRightPage);

		//5、获取指向该page的内部节点,在其中添加一个指向page和newRightPage的新entry。将父entry所在的page添加到dirtypages中。
		BTreeInternalPage parent = getParentWithEmptySlots(tid,dirtypages,page.getParentId(),field);
		Field mid = newRightPage.iterator().next().getField(keyField);
		BTreeEntry entry = new BTreeEntry(mid,page.getId(),newRightPage.getId());
		parent.insertEntry(entry);
		dirtypages.put(parent.getId(),parent);

		//6、更新page、newRightPage的父指针。
		updateParentPointers(tid, dirtypages, parent);

		//7、返回field所在的页(page或newRightPage)
		if(field.compare(Op.GREATER_THAN_OR_EQ,mid)){
    
    
			return newRightPage;
		}
		return page;
		
	}

Reimplementar nodos internos divididos

	public BTreeInternalPage splitInternalPage(TransactionId tid, Map<PageId, Page> dirtypages,
			BTreeInternalPage page, Field field) 
					throws DbException, IOException, TransactionAbortedException {
    
    
		// some code goes here
        //
        // Split the internal page by adding a new page on the right of the existing
		// page and moving half of the entries to the new page.  Push the middle key up
		// into the parent page, and recursively split the parent as needed to accommodate
		// the new entry.  getParentWithEmtpySlots() will be useful here.  Don't forget to update
		// the parent pointers of all the children moving to the new page.  updateParentPointers()
		// will be useful here.  Return the page into which an entry with the given key field
		// should be inserted.

		// 1、通过getEmptyPage创建一个newRightPage。
		BTreeInternalPage newRightPage = (BTreeInternalPage) getEmptyPage(tid,dirtypages,BTreePageId.INTERNAL);
		Iterator<BTreeEntry> it = page.reverseIterator();

		// 2、将当前page中一半的entry插入到newRightPage中。同样,先从page中删除entry,再将其插入到newRightPage中。
		int tuplesNum = page.getNumEntries();
		for(int i=0; i<tuplesNum / 2; i++){
    
    
			BTreeEntry entry = it.next();
			page.deleteKeyAndRightChild(entry);
			newRightPage.insertEntry(entry);	//当entry被添加到newRightPage之后它的recordId被更改了,再在page中删除,是找不到这个entry的
			//所以只能先删除再插入到新的Page中
		}

		// 3、分配完entry后,选出page中最大的entry,将其从page中删除,并将该entry的左孩子指针指向page,右孩子指针指向newRightPage,
		// 获取父节点parent,将该entry添加到父节点中(实现将中间的key“挤到”父节点中)。
		BTreeEntry mid = it.next();
		page.deleteKeyAndRightChild(mid);
		mid.setLeftChild(page.getId());
		mid.setRightChild(newRightPage.getId());
		BTreeInternalPage parent = getParentWithEmptySlots(tid,dirtypages,page.getParentId(),mid.getKey());
		parent.insertEntry(mid);

		// 4、将父节点parent、page、newRightPage添加到dirtypages中,并更新它们孩子节点的父指针。
		dirtypages.put(parent.getId(), parent);
		dirtypages.put(page.getId(), page);
		dirtypages.put(newRightPage.getId(),newRightPage);
		updateParentPointers(tid, dirtypages, parent);
		updateParentPointers(tid,dirtypages,page);
		updateParentPointers(tid,dirtypages,newRightPage);

		// 5、返回field所在的页(page或newRightPage)。
		if(field.compare(Op.GREATER_THAN_OR_EQ, mid.getKey())){
    
    
			return newRightPage;
		}
		return page;


	}

3.Ejercicio 3

Para mantener el árbol equilibrado y no desperdiciar espacio innecesario, las eliminaciones en B+Tree pueden causar tuplas de reasignación de página o una eventual fusión.

inserte la descripción de la imagen aquí
reasignar páginas
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
Intentar eliminar una tupla de una página hoja que no está llena a la mitad debería causar que esa página robe la tupla de uno de sus hermanos o se fusione con uno de sus hermanos. Si una de las páginas hermanas de la página tiene tuplas alternativas, las tuplas deben distribuirse uniformemente entre las dos páginas y las entradas de la página principal deben actualizarse en consecuencia.

Sin embargo, si la página hermana también tiene una ocupación mínima, las dos páginas deben fusionarse y la entrada debe eliminarse de la página principal. Por el contrario, eliminar una entrada de un padre puede hacer que el padre se llene menos de la mitad. En este caso, el padre debe robar la entrada a su hermano o fusionarse con el hermano. Esto puede dar lugar a fusiones recursivas e incluso a la eliminación del nodo raíz si se elimina la última entrada del nodo raíz.

Implementación de robeFromLeafPage:

	public void stealFromLeafPage(BTreeLeafPage page, BTreeLeafPage sibling,
			BTreeInternalPage parent, BTreeEntry entry, boolean isRightSibling) throws DbException {
    
    
		// some code goes here
        //
        // Move some of the tuples from the sibling to the page so
		// that the tuples are evenly distributed. Be sure to update
		// the corresponding parent entry.

		// 1、根据传入的参数isRightSibling确定是从左兄弟中“窃取”,还是从右兄弟中“窃取”。
		Iterator<Tuple> it = isRightSibling ? sibling.iterator() : sibling.reverseIterator();

		// 2、根据兄弟节点中tuple的数量,确定“窃取的数量”。
		int curTuplesNum = page.getNumTuples();
		int siblingTuplesNum = sibling.getNumTuples();
		int targetTuplesNum = (curTuplesNum + siblingTuplesNum) / 2;
		while(curTuplesNum < targetTuplesNum){
    
    
			Tuple tuple = it.next();
			sibling.deleteTuple(tuple);
			page.insertTuple(tuple);
			curTuplesNum++;
		}

		// 3、参数entry是父节点中指向page和其兄弟节点的entry,将entry的key更改为page和其兄弟节点key的中间值。
		Tuple mid = it.next();
		entry.setKey(mid.getField(keyField));
		parent.updateEntry(entry);

	}

Robar la implementación de InternalPage:

	public void stealFromLeftInternalPage(TransactionId tid, Map<PageId, Page> dirtypages,
			BTreeInternalPage page, BTreeInternalPage leftSibling, BTreeInternalPage parent,
			BTreeEntry parentEntry) throws DbException, TransactionAbortedException {
    
    
		// some code goes here
        // Move some of the entries from the left sibling to the page so
		// that the entries are evenly distributed. Be sure to update
		// the corresponding parent entry. Be sure to update the parent
		// pointers of all children in the entries that were moved.

		//1、根据page及其左兄弟中key的数量,确定从其做兄弟中“窃取”几个key。
		Iterator<BTreeEntry> it = leftSibling.reverseIterator();
		int curEntriesNum = page.getNumEntries();
		int siblingEntriesNum = leftSibling.getNumEntries();
		int targetEntriesNum = (curEntriesNum + siblingEntriesNum) / 2;

		//2、因为内部节点与其父节点中的key值没有重复,迁移key的时候也需要将父节点中的key移动到page中。
		BTreeEntry entry = it.next();
		BTreeEntry mid = new BTreeEntry(parentEntry.getKey(),entry.getRightChild(),page.iterator().next().getLeftChild());
		page.insertEntry(mid);
		curEntriesNum++;

		//3、将page左兄弟节点中的key平均分配。
		while(curEntriesNum < targetEntriesNum){
    
    

			leftSibling.deleteKeyAndRightChild(entry);
			page.insertEntry(entry);
			curEntriesNum++;
			entry = it.next();
		}

		//4、分配之后,将page左兄弟节点中最大的key“挤到”父节点中。
		leftSibling.deleteKeyAndRightChild(entry);
		parentEntry.setKey(entry.getKey());
		parent.updateEntry(parentEntry);

		//5、更新更新page与其左兄弟的父指针。
		dirtypages.put(page.getId(),page);
		dirtypages.put(leftSibling.getId(),leftSibling);
		dirtypages.put(parent.getId(),parent);
		updateParentPointers(tid,dirtypages,page);
	}

	public void stealFromRightInternalPage(TransactionId tid, Map<PageId, Page> dirtypages,
			BTreeInternalPage page, BTreeInternalPage rightSibling, BTreeInternalPage parent,
			BTreeEntry parentEntry) throws DbException, TransactionAbortedException {
    
    
		// some code goes here
        // Move some of the entries from the right sibling to the page so
		// that the entries are evenly distributed. Be sure to update
		// the corresponding parent entry. Be sure to update the parent
		// pointers of all children in the entries that were moved.

		Iterator<BTreeEntry> it = rightSibling.iterator();
		int curEntriesNum = page.getNumEntries();
		int siblingEntriesNum = rightSibling.getNumEntries();
		int targetEntriesNum = (curEntriesNum + siblingEntriesNum) / 2;

		BTreeEntry entry = it.next();
		BTreeEntry mid = new BTreeEntry(parentEntry.getKey(), page.reverseIterator().next().getRightChild(), entry.getLeftChild());
		page.insertEntry(mid);
		curEntriesNum++;

		while(curEntriesNum < targetEntriesNum){
    
    
			rightSibling.deleteKeyAndLeftChild(entry);
			page.insertEntry(entry);
			entry = it.next();
			curEntriesNum++;
		}


		rightSibling.deleteKeyAndLeftChild(entry);
		parentEntry.setKey(entry.getKey());
		parent.updateEntry(parentEntry);
		dirtypages.put(page.getId(),page);
		dirtypages.put(rightSibling.getId(),rightSibling);
		dirtypages.put(parent.getId(),parent);
		updateParentPointers(tid,dirtypages,page);
	}

4.Ejercicio 4

Implementar mergeLeafPages() y mergeInternalPages()

	public void mergeLeafPages(TransactionId tid, Map<PageId, Page> dirtypages,
			BTreeLeafPage leftPage, BTreeLeafPage rightPage, BTreeInternalPage parent, BTreeEntry parentEntry) 
					throws DbException, IOException, TransactionAbortedException {
    
    

		// some code goes here
        //
		// Move all the tuples from the right page to the left page, update
		// the sibling pointers, and make the right page available for reuse.
		// Delete the entry in the parent corresponding to the two pages that are merging -
		// deleteParentEntry() will be useful here

		//1、将rightPage中的所有tuple添加到leftPage中。
		Iterator<Tuple> it = rightPage.iterator();
		while(it.hasNext()){
    
    
			Tuple tuple = it.next();
			rightPage.deleteTuple(tuple);
			leftPage.insertTuple(tuple);
		}

		//2、判断rightPage是否有右兄弟,如果没有leftPage的右兄弟为空,如果有leftPage的右兄弟指向rightPage的右兄弟。
		BTreePageId rightPageRightSiblingId = rightPage.getRightSiblingId();
		if(rightPageRightSiblingId == null){
    
    
			leftPage.setRightSiblingId(null);
		}
		else{
    
    
			leftPage.setRightSiblingId(rightPageRightSiblingId);
			BTreeLeafPage rightPageRightSibling = (BTreeLeafPage) getPage(tid,dirtypages,rightPageRightSiblingId,Permissions.READ_WRITE);
			rightPageRightSibling.setLeftSiblingId(leftPage.getId());
		}

		//3、调用setEmptyPage方法将rightPage在header标记为空。
		setEmptyPage(tid, dirtypages, rightPage.pid.getPageNumber());	//将rightPage在header处置空

		//4、调用deleteParentEntry方法,从父级中删除左右孩子指针指向leftPage和rightPage的entry。
		deleteParentEntry(tid, dirtypages, leftPage, parent, parentEntry);

		//5、将leftPage与parent添加到dirtypages中
		dirtypages.put(leftPage.getId(),leftPage);
		dirtypages.put(parent.getId(),parent);
	}

	public void mergeInternalPages(TransactionId tid, Map<PageId, Page> dirtypages,
			BTreeInternalPage leftPage, BTreeInternalPage rightPage, BTreeInternalPage parent, BTreeEntry parentEntry) 
					throws DbException, IOException, TransactionAbortedException {
    
    
		
		// some code goes here
        //
        // Move all the entries from the right page to the left page, update
		// the parent pointers of the children in the entries that were moved, 
		// and make the right page available for reuse
		// Delete the entry in the parent corresponding to the two pages that are merging -
		// deleteParentEntry() will be useful here

		//1、先将父节点中的指向leftPage和rightPage的entry添加到leftPage中
		BTreeEntry mid = new BTreeEntry(parentEntry.getKey(),leftPage.reverseIterator().next().getRightChild(),rightPage.iterator().next().getLeftChild());
		leftPage.insertEntry(mid);

		//2、将rightPage中的entry添加到leftPage中
		Iterator<BTreeEntry> rightIt = rightPage.iterator();
		while (rightIt.hasNext()){
    
    
			BTreeEntry entry = rightIt.next();
			rightPage.deleteKeyAndLeftChild(entry);
			leftPage.insertEntry(entry);
		}

		//3、更新leftPage孩子节点的指针(将原本父节点指向rightPage的孩子节点的父节点更新为leftPage)
		updateParentPointers(tid,dirtypages,leftPage);

		//4、调用setEmptyPage方法将rightPage在header标记为空。
		setEmptyPage(tid,dirtypages,rightPage.getId().getPageNumber());

		//5、调用deleteParentEntry方法,从父级中删除左右孩子指针指向leftPage和rightPage的entry。
		deleteParentEntry(tid,dirtypages,leftPage,parent,parentEntry);

		//6、将leftPage与parent添加到dirtypages中
		dirtypages.put(leftPage.getId(),leftPage);
		dirtypages.put(parent.getId(),parent);
	}

Resumir

Las notas de clase de lab5 son realmente importantes. Explican mucha lógica claramente y también proporcionan muchos métodos que se pueden llamar directamente. Si empiezas desde 0, creo que lab5 puede ser la más difícil, pero si entiendes la estructura de el árbol B+, hazlo, no es tan incómodo. Aún queda el último informe de laboratorio, ¡vamos!

Supongo que te gusta

Origin blog.csdn.net/weixin_44153131/article/details/128864477
Recomendado
Clasificación